From 84b72deb5927841db020ec427ee3129d5ab71550 Mon Sep 17 00:00:00 2001 From: sistemd Date: Fri, 15 Mar 2024 01:29:33 +0100 Subject: [PATCH 01/24] store via bincode instead of json --- crates/storage/Cargo.toml | 2 +- crates/storage/src/connection/transaction.rs | 771 ++++---- crates/storage/src/fake.rs | 16 +- crates/storage/src/schema/revision_0051.rs | 1694 +++++++++++++++++- 4 files changed, 2033 insertions(+), 450 deletions(-) diff --git a/crates/storage/Cargo.toml b/crates/storage/Cargo.toml index a5187c7239..f1a13aee45 100644 --- a/crates/storage/Cargo.toml +++ b/crates/storage/Cargo.toml @@ -10,7 +10,7 @@ rust-version = { workspace = true } [dependencies] anyhow = { workspace = true } base64 = { workspace = true } -bincode = "2.0.0-rc.3" +bincode = { version = "2.0.0-rc.3", features = ["serde"] } bitvec = { workspace = true } bloomfilter = "1.0.12" cached = { workspace = true } diff --git a/crates/storage/src/connection/transaction.rs b/crates/storage/src/connection/transaction.rs index a6f38927d0..e046715cd3 100644 --- a/crates/storage/src/connection/transaction.rs +++ b/crates/storage/src/connection/transaction.rs @@ -1,7 +1,6 @@ //! Contains starknet transaction related code and __not__ database transaction. use anyhow::Context; -use pathfinder_common::event::Event; use pathfinder_common::receipt::Receipt; use pathfinder_common::transaction::Transaction as StarknetTransaction; use pathfinder_common::{BlockHash, BlockNumber, TransactionHash}; @@ -38,7 +37,8 @@ pub(super) fn insert_transactions( // Serialize and compress transaction data. let transaction = dto::Transaction::from(transaction); - let tx_data = serde_json::to_vec(&transaction).context("Serializing transaction")?; + let tx_data = bincode::serde::encode_to_vec(&transaction, bincode::config::standard()) + .context("Serializing transaction")?; let tx_data = compressor .compress(&tx_data) .context("Compressing transaction")?; @@ -47,7 +47,8 @@ pub(super) fn insert_transactions( Some(receipt) => { let receipt = dto::Receipt::from(receipt); let serialized_receipt = - serde_json::to_vec(&receipt).context("Serializing receipt")?; + bincode::serde::encode_to_vec(&receipt, bincode::config::standard()) + .context("Serializing receipt")?; Some( compressor .compress(&serialized_receipt) @@ -59,7 +60,13 @@ pub(super) fn insert_transactions( let serialized_events = match events { Some(events) => { - let events = serde_json::to_vec(&events).context("Serializing events")?; + let events = bincode::serde::encode_to_vec( + &dto::Events::V0 { + events: events.clone(), + }, + bincode::config::standard(), + ) + .context("Serializing events")?; Some(compressor.compress(&events).context("Compressing events")?) } None => None, @@ -94,14 +101,21 @@ pub(super) fn update_receipt( ) -> anyhow::Result<()> { let mut compressor = zstd::bulk::Compressor::new(10).context("Create zstd compressor")?; let receipt = dto::Receipt::from(receipt); - let serialized_receipt = serde_json::to_vec(&receipt).context("Serializing receipt")?; + let serialized_receipt = bincode::serde::encode_to_vec(&receipt, bincode::config::standard()) + .context("Serializing receipt")?; let serialized_receipt = compressor .compress(&serialized_receipt) .context("Compressing receipt")?; - let execution_status = match receipt.execution_status { - dto::ExecutionStatus::Succeeded => 0, - dto::ExecutionStatus::Reverted { .. } => 1, + let execution_status = match receipt { + dto::Receipt::V0(dto::ReceiptV0 { + execution_status: dto::ExecutionStatus::Succeeded, + .. + }) => 0, + dto::Receipt::V0(dto::ReceiptV0 { + execution_status: dto::ExecutionStatus::Reverted { .. }, + .. + }) => 1, }; tx.inner() @@ -143,7 +157,9 @@ pub(super) fn transaction( let transaction = row.get_ref_unwrap(0).as_blob()?; let transaction = zstd::decode_all(transaction).context("Decompressing transaction")?; let transaction: dto::Transaction = - serde_json::from_slice(&transaction).context("Deserializing transaction")?; + bincode::serde::decode_from_slice(&transaction, bincode::config::standard()) + .context("Deserializing transaction")? + .0; Ok(Some(transaction.into())) } @@ -173,7 +189,9 @@ pub(super) fn transaction_with_receipt( let transaction = row.get_ref_unwrap("tx").as_blob()?; let transaction = zstd::decode_all(transaction).context("Decompressing transaction")?; let transaction: dto::Transaction = - serde_json::from_slice(&transaction).context("Deserializing transaction")?; + bincode::serde::decode_from_slice(&transaction, bincode::config::standard()) + .context("Deserializing transaction")? + .0; let receipt = match row.get_ref_unwrap("receipt").as_blob_or_null()? { Some(data) => data, @@ -181,22 +199,28 @@ pub(super) fn transaction_with_receipt( }; let receipt = zstd::decode_all(receipt).context("Decompressing receipt")?; let receipt: dto::Receipt = - serde_json::from_slice(&receipt).context("Deserializing receipt")?; + bincode::serde::decode_from_slice(&receipt, bincode::config::standard()) + .context("Deserializing receipt")? + .0; let events = match row.get_ref_unwrap("events").as_blob_or_null()? { Some(data) => data, None => return Ok(None), }; let events = zstd::decode_all(events).context("Decompressing events")?; - let events: Vec = - serde_json::from_slice(&events).context("Deserializing events")?; + let events: dto::Events = + bincode::serde::decode_from_slice(&events, bincode::config::standard()) + .context("Deserializing events")? + .0; let block_hash = row.get_block_hash("block_hash")?; Ok(Some(( transaction.into(), receipt.into(), - events, + match events { + dto::Events::V0 { events } => events, + }, block_hash, ))) } @@ -232,7 +256,9 @@ pub(super) fn transaction_at_block( let transaction = zstd::decode_all(transaction).context("Decompressing transaction")?; let transaction: dto::Transaction = - serde_json::from_slice(&transaction).context("Deserializing transaction")?; + bincode::serde::decode_from_slice(&transaction, bincode::config::standard()) + .context("Deserializing transaction")? + .0; Ok(Some(transaction.into())) } @@ -301,7 +327,9 @@ pub(super) fn transaction_data_for_block( .context("Receipt data missing")?; let receipt = zstd::decode_all(receipt).context("Decompressing transaction receipt")?; let receipt: dto::Receipt = - serde_json::from_slice(&receipt).context("Deserializing transaction receipt")?; + bincode::serde::decode_from_slice(&receipt, bincode::config::standard()) + .context("Deserializing transaction receipt")? + .0; let transaction = row .get_ref_unwrap("tx") @@ -309,16 +337,27 @@ pub(super) fn transaction_data_for_block( .context("Transaction data missing")?; let transaction = zstd::decode_all(transaction).context("Decompressing transaction")?; let transaction: dto::Transaction = - serde_json::from_slice(&transaction).context("Deserializing transaction")?; + bincode::serde::decode_from_slice(&transaction, bincode::config::standard()) + .context("Deserializing transaction")? + .0; let events = row .get_ref_unwrap("events") .as_blob_or_null()? .context("Events missing")?; let events = zstd::decode_all(events).context("Decompressing events")?; - let events: Vec = serde_json::from_slice(&events).context("Deserializing events")?; - - data.push((transaction.into(), receipt.into(), events)); + let events: dto::Events = + bincode::serde::decode_from_slice(&events, bincode::config::standard()) + .context("Deserializing events")? + .0; + + data.push(( + transaction.into(), + receipt.into(), + match events { + dto::Events::V0 { events } => events, + }, + )); } Ok(Some(data)) @@ -349,7 +388,9 @@ pub(super) fn transactions_for_block( .context("Transaction data missing")?; let transaction = zstd::decode_all(transaction).context("Decompressing transaction")?; let transaction: dto::Transaction = - serde_json::from_slice(&transaction).context("Deserializing transaction")?; + bincode::serde::decode_from_slice(&transaction, bincode::config::standard()) + .context("Deserializing transaction")? + .0; data.push(transaction.into()); } @@ -391,10 +432,17 @@ pub(super) fn events_for_block( .as_blob_or_null()? .context("Transaction events missing")?; let events = zstd::decode_all(events).context("Decompressing events")?; - let events: Vec = - serde_json::from_slice(&events).context("Deserializing events")?; - - data.push((hash, events)); + let events: dto::Events = + bincode::serde::decode_from_slice(&events, bincode::config::standard()) + .context("Deserializing events")? + .0; + + data.push(( + hash, + match events { + dto::Events::V0 { events } => events, + }, + )); } Ok(Some(data)) @@ -435,8 +483,6 @@ pub(super) fn transaction_block_hash( .map_err(|e| e.into()) } -/// A copy of the gateway definitions which are currently used as the storage serde implementation. Having a copy here -/// allows us to decouple this crate from the gateway types, while only exposing the common types via the storage API. pub(crate) mod dto { use fake::{Dummy, Fake, Faker}; use pathfinder_common::*; @@ -478,6 +524,16 @@ pub(crate) mod dto { } } + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] + #[serde(deny_unknown_fields)] + #[serde(tag = "db_version")] + pub enum Events { + #[serde(rename = "0")] + V0 { + events: Vec, + }, + } + /// Represents execution resources for L2 transaction. #[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] @@ -680,17 +736,22 @@ pub(crate) mod dto { Reverted, } - /// Represents deserialized L2 transaction receipt data. #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] - pub struct Receipt { + #[serde(tag = "db_version")] + pub enum Receipt { + #[serde(rename = "0")] + V0(ReceiptV0), + } + + /// Represents deserialized L2 transaction receipt data. + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] + #[serde(deny_unknown_fields)] + pub struct ReceiptV0 { #[serde(default)] pub actual_fee: Option, #[serde(skip_serializing_if = "Option::is_none")] pub execution_resources: Option, - // This field exists in our database but is unused within our code. - // It is redundant data that is also contained in the L1 handler. - pub l1_to_l2_consumed_message: Option, pub l2_to_l1_messages: Vec, pub transaction_hash: TransactionHash, pub transaction_index: TransactionIndex, @@ -707,17 +768,16 @@ pub(crate) mod dto { fn from(value: Receipt) -> Self { use pathfinder_common::receipt as common; - let Receipt { + let Receipt::V0(ReceiptV0 { actual_fee, execution_resources, // This information is redundant as it is already in the transaction itself. - l1_to_l2_consumed_message: _, l2_to_l1_messages, transaction_hash, transaction_index, execution_status, revert_error, - } = value; + }) = value; common::Receipt { actual_fee, @@ -744,17 +804,15 @@ pub(crate) mod dto { } }; - Self { + Self::V0(ReceiptV0 { actual_fee: value.actual_fee, execution_resources: Some((&value.execution_resources).into()), - // We don't care about this field anymore. - l1_to_l2_consumed_message: None, l2_to_l1_messages: value.l2_to_l1_messages.iter().map(Into::into).collect(), transaction_hash: value.transaction_hash, transaction_index: value.transaction_index, execution_status, revert_error, - } + }) } } @@ -765,16 +823,15 @@ pub(crate) mod dto { (execution_status == ExecutionStatus::Reverted).then(|| Faker.fake_with_rng(rng)); // Those fields that were missing in very old receipts are always present - Self { + Self::V0(ReceiptV0 { actual_fee: Some(Faker.fake_with_rng(rng)), execution_resources: Some(Faker.fake_with_rng(rng)), - l1_to_l2_consumed_message: Faker.fake_with_rng(rng), l2_to_l1_messages: Faker.fake_with_rng(rng), transaction_hash: Faker.fake_with_rng(rng), transaction_index: Faker.fake_with_rng(rng), execution_status, revert_error, - } + }) } } @@ -881,11 +938,19 @@ pub(crate) mod dto { } } + #[derive(Clone, Debug, Serialize, PartialEq, Eq, Dummy)] + #[serde(tag = "db_version")] + #[serde(deny_unknown_fields)] + pub enum Transaction { + #[serde(rename = "0")] + V0(TransactionV0), + } + /// Represents deserialized L2 transaction data. #[derive(Clone, Debug, Serialize, PartialEq, Eq, Dummy)] #[serde(tag = "type")] #[serde(deny_unknown_fields)] - pub enum Transaction { + pub enum TransactionV0 { #[serde(rename = "DECLARE")] Declare(DeclareTransaction), #[serde(rename = "DEPLOY")] @@ -935,9 +1000,11 @@ pub(crate) mod dto { let tx = InnerTransaction::deserialize(deserializer)?; let tx = match tx { - InnerTransaction::Declare(x) => Transaction::Declare(x), - InnerTransaction::Deploy(x) => Transaction::Deploy(x), - InnerTransaction::DeployAccount(x) => Transaction::DeployAccount(x), + InnerTransaction::Declare(x) => Transaction::V0(TransactionV0::Declare(x)), + InnerTransaction::Deploy(x) => Transaction::V0(TransactionV0::Deploy(x)), + InnerTransaction::DeployAccount(x) => { + Transaction::V0(TransactionV0::DeployAccount(x)) + } InnerTransaction::Invoke(InvokeTransaction::V0(i)) if i.entry_point_type == Some(EntryPointType::L1Handler) => { @@ -950,10 +1017,10 @@ pub(crate) mod dto { version: TransactionVersion::ZERO, }; - Transaction::L1Handler(l1_handler) + Transaction::V0(TransactionV0::L1Handler(l1_handler)) } - InnerTransaction::Invoke(x) => Transaction::Invoke(x), - InnerTransaction::L1Handler(x) => Transaction::L1Handler(x), + InnerTransaction::Invoke(x) => Transaction::V0(TransactionV0::Invoke(x)), + InnerTransaction::L1Handler(x) => Transaction::V0(TransactionV0::L1Handler(x)), }; Ok(tx) @@ -973,28 +1040,32 @@ pub(crate) mod dto { nonce, sender_address, signature, - }) => Self::Declare(DeclareTransaction::V0(self::DeclareTransactionV0V1 { - class_hash, - max_fee, - nonce, - sender_address, - signature, - transaction_hash, - })), + }) => Self::V0(TransactionV0::Declare(DeclareTransaction::V0( + self::DeclareTransactionV0V1 { + class_hash, + max_fee, + nonce, + sender_address, + signature, + transaction_hash, + }, + ))), DeclareV1(DeclareTransactionV0V1 { class_hash, max_fee, nonce, sender_address, signature, - }) => Self::Declare(DeclareTransaction::V1(self::DeclareTransactionV0V1 { - class_hash, - max_fee, - nonce, - sender_address, - signature, - transaction_hash, - })), + }) => Self::V0(TransactionV0::Declare(DeclareTransaction::V1( + self::DeclareTransactionV0V1 { + class_hash, + max_fee, + nonce, + sender_address, + signature, + transaction_hash, + }, + ))), DeclareV2(DeclareTransactionV2 { class_hash, max_fee, @@ -1002,15 +1073,17 @@ pub(crate) mod dto { sender_address, signature, compiled_class_hash, - }) => Self::Declare(DeclareTransaction::V2(self::DeclareTransactionV2 { - class_hash, - max_fee, - nonce, - sender_address, - signature, - transaction_hash, - compiled_class_hash, - })), + }) => Self::V0(TransactionV0::Declare(DeclareTransaction::V2( + self::DeclareTransactionV2 { + class_hash, + max_fee, + nonce, + sender_address, + signature, + transaction_hash, + compiled_class_hash, + }, + ))), DeclareV3(DeclareTransactionV3 { class_hash, nonce, @@ -1023,34 +1096,59 @@ pub(crate) mod dto { account_deployment_data, sender_address, compiled_class_hash, - }) => Self::Declare(DeclareTransaction::V3(self::DeclareTransactionV3 { - class_hash, - nonce, - nonce_data_availability_mode: nonce_data_availability_mode.into(), - fee_data_availability_mode: fee_data_availability_mode.into(), - resource_bounds: resource_bounds.into(), - tip, - paymaster_data, - sender_address, - signature, - transaction_hash, - compiled_class_hash, - account_deployment_data, - })), + }) => Self::V0(TransactionV0::Declare(DeclareTransaction::V3( + self::DeclareTransactionV3 { + class_hash, + nonce, + nonce_data_availability_mode: nonce_data_availability_mode.into(), + fee_data_availability_mode: fee_data_availability_mode.into(), + resource_bounds: resource_bounds.into(), + tip, + paymaster_data, + sender_address, + signature, + transaction_hash, + compiled_class_hash, + account_deployment_data, + }, + ))), Deploy(DeployTransaction { contract_address, contract_address_salt, class_hash, constructor_calldata, version, - }) => Self::Deploy(self::DeployTransaction { + }) => Self::V0(TransactionV0::Deploy(self::DeployTransaction { contract_address, contract_address_salt, class_hash, constructor_calldata, transaction_hash, version, - }), + })), + DeployAccountV0V1(DeployAccountTransactionV0V1 { + contract_address, + max_fee, + version, + signature, + nonce, + contract_address_salt, + constructor_calldata, + class_hash, + }) if version == TransactionVersion::ZERO => { + Self::V0(TransactionV0::DeployAccount( + self::DeployAccountTransaction::V0(self::DeployAccountTransactionV0V1 { + contract_address, + transaction_hash, + max_fee, + signature, + nonce, + contract_address_salt, + constructor_calldata, + class_hash, + }), + )) + } DeployAccountV0V1(DeployAccountTransactionV0V1 { contract_address, max_fee, @@ -1060,19 +1158,21 @@ pub(crate) mod dto { contract_address_salt, constructor_calldata, class_hash, - }) => Self::DeployAccount(self::DeployAccountTransaction::V0V1( - self::DeployAccountTransactionV0V1 { + }) if version == TransactionVersion::ONE => Self::V0(TransactionV0::DeployAccount( + self::DeployAccountTransaction::V1(self::DeployAccountTransactionV0V1 { contract_address, transaction_hash, max_fee, - version, signature, nonce, contract_address_salt, constructor_calldata, class_hash, - }, + }), )), + DeployAccountV0V1(DeployAccountTransactionV0V1 { version, .. }) => { + panic!("invalid version {version:?}") + } DeployAccountV3(DeployAccountTransactionV3 { contract_address, signature, @@ -1085,8 +1185,8 @@ pub(crate) mod dto { contract_address_salt, constructor_calldata, class_hash, - }) => Self::DeployAccount(self::DeployAccountTransaction::V3( - self::DeployAccountTransactionV3 { + }) => Self::V0(TransactionV0::DeployAccount( + self::DeployAccountTransaction::V3(self::DeployAccountTransactionV3 { nonce, nonce_data_availability_mode: nonce_data_availability_mode.into(), fee_data_availability_mode: fee_data_availability_mode.into(), @@ -1096,11 +1196,10 @@ pub(crate) mod dto { sender_address: contract_address, signature, transaction_hash, - version: TransactionVersion::THREE, contract_address_salt, constructor_calldata, class_hash, - }, + }), )), InvokeV0(InvokeTransactionV0 { calldata, @@ -1109,29 +1208,33 @@ pub(crate) mod dto { entry_point_type, max_fee, signature, - }) => Self::Invoke(InvokeTransaction::V0(self::InvokeTransactionV0 { - calldata, - sender_address, - entry_point_selector, - entry_point_type: entry_point_type.map(Into::into), - max_fee, - signature, - transaction_hash, - })), + }) => Self::V0(TransactionV0::Invoke(InvokeTransaction::V0( + self::InvokeTransactionV0 { + calldata, + sender_address, + entry_point_selector, + entry_point_type: entry_point_type.map(Into::into), + max_fee, + signature, + transaction_hash, + }, + ))), InvokeV1(InvokeTransactionV1 { calldata, sender_address, max_fee, signature, nonce, - }) => Self::Invoke(InvokeTransaction::V1(self::InvokeTransactionV1 { - calldata, - sender_address, - max_fee, - signature, - nonce, - transaction_hash, - })), + }) => Self::V0(TransactionV0::Invoke(InvokeTransaction::V1( + self::InvokeTransactionV1 { + calldata, + sender_address, + max_fee, + signature, + nonce, + transaction_hash, + }, + ))), InvokeV3(InvokeTransactionV3 { signature, nonce, @@ -1143,32 +1246,34 @@ pub(crate) mod dto { account_deployment_data, calldata, sender_address, - }) => Self::Invoke(InvokeTransaction::V3(self::InvokeTransactionV3 { - nonce, - nonce_data_availability_mode: nonce_data_availability_mode.into(), - fee_data_availability_mode: fee_data_availability_mode.into(), - resource_bounds: resource_bounds.into(), - tip, - paymaster_data, - sender_address, - signature, - transaction_hash, - calldata, - account_deployment_data, - })), + }) => Self::V0(TransactionV0::Invoke(InvokeTransaction::V3( + self::InvokeTransactionV3 { + nonce, + nonce_data_availability_mode: nonce_data_availability_mode.into(), + fee_data_availability_mode: fee_data_availability_mode.into(), + resource_bounds: resource_bounds.into(), + tip, + paymaster_data, + sender_address, + signature, + transaction_hash, + calldata, + account_deployment_data, + }, + ))), L1Handler(L1HandlerTransaction { contract_address, entry_point_selector, nonce, calldata, - }) => Self::L1Handler(self::L1HandlerTransaction { + }) => Self::V0(TransactionV0::L1Handler(self::L1HandlerTransaction { contract_address, entry_point_selector, nonce, calldata, transaction_hash, version: TransactionVersion::ZERO, - }), + })), } } } @@ -1179,14 +1284,16 @@ pub(crate) mod dto { let hash = value.hash(); let variant = match value { - Transaction::Declare(DeclareTransaction::V0(DeclareTransactionV0V1 { - class_hash, - max_fee, - nonce, - sender_address, - signature, - transaction_hash: _, - })) => TransactionVariant::DeclareV0( + Transaction::V0(TransactionV0::Declare(DeclareTransaction::V0( + DeclareTransactionV0V1 { + class_hash, + max_fee, + nonce, + sender_address, + signature, + transaction_hash: _, + }, + ))) => TransactionVariant::DeclareV0( pathfinder_common::transaction::DeclareTransactionV0V1 { class_hash, max_fee, @@ -1195,14 +1302,16 @@ pub(crate) mod dto { signature, }, ), - Transaction::Declare(DeclareTransaction::V1(DeclareTransactionV0V1 { - class_hash, - max_fee, - nonce, - sender_address, - signature, - transaction_hash: _, - })) => TransactionVariant::DeclareV1( + Transaction::V0(TransactionV0::Declare(DeclareTransaction::V1( + DeclareTransactionV0V1 { + class_hash, + max_fee, + nonce, + sender_address, + signature, + transaction_hash: _, + }, + ))) => TransactionVariant::DeclareV1( pathfinder_common::transaction::DeclareTransactionV0V1 { class_hash, max_fee, @@ -1211,15 +1320,17 @@ pub(crate) mod dto { signature, }, ), - Transaction::Declare(DeclareTransaction::V2(DeclareTransactionV2 { - class_hash, - max_fee, - nonce, - sender_address, - signature, - transaction_hash: _, - compiled_class_hash, - })) => TransactionVariant::DeclareV2( + Transaction::V0(TransactionV0::Declare(DeclareTransaction::V2( + DeclareTransactionV2 { + class_hash, + max_fee, + nonce, + sender_address, + signature, + transaction_hash: _, + compiled_class_hash, + }, + ))) => TransactionVariant::DeclareV2( pathfinder_common::transaction::DeclareTransactionV2 { class_hash, max_fee, @@ -1229,20 +1340,22 @@ pub(crate) mod dto { compiled_class_hash, }, ), - Transaction::Declare(DeclareTransaction::V3(DeclareTransactionV3 { - class_hash, - nonce, - nonce_data_availability_mode, - fee_data_availability_mode, - resource_bounds, - tip, - paymaster_data, - sender_address, - signature, - transaction_hash: _, - compiled_class_hash, - account_deployment_data, - })) => TransactionVariant::DeclareV3( + Transaction::V0(TransactionV0::Declare(DeclareTransaction::V3( + DeclareTransactionV3 { + class_hash, + nonce, + nonce_data_availability_mode, + fee_data_availability_mode, + resource_bounds, + tip, + paymaster_data, + sender_address, + signature, + transaction_hash: _, + compiled_class_hash, + account_deployment_data, + }, + ))) => TransactionVariant::DeclareV3( pathfinder_common::transaction::DeclareTransactionV3 { class_hash, nonce, @@ -1257,14 +1370,14 @@ pub(crate) mod dto { account_deployment_data, }, ), - Transaction::Deploy(DeployTransaction { + Transaction::V0(TransactionV0::Deploy(DeployTransaction { contract_address, contract_address_salt, class_hash, constructor_calldata, transaction_hash: _, version, - }) => { + })) => { TransactionVariant::Deploy(pathfinder_common::transaction::DeployTransaction { contract_address, contract_address_salt, @@ -1273,23 +1386,45 @@ pub(crate) mod dto { version, }) } - Transaction::DeployAccount(DeployAccountTransaction::V0V1( + Transaction::V0(TransactionV0::DeployAccount(DeployAccountTransaction::V0( DeployAccountTransactionV0V1 { contract_address, transaction_hash: _, max_fee, - version, signature, nonce, contract_address_salt, constructor_calldata, class_hash, }, - )) => TransactionVariant::DeployAccountV0V1( + ))) => TransactionVariant::DeployAccountV0V1( pathfinder_common::transaction::DeployAccountTransactionV0V1 { contract_address, max_fee, - version, + version: TransactionVersion::ZERO, + signature, + nonce, + contract_address_salt, + constructor_calldata, + class_hash, + }, + ), + Transaction::V0(TransactionV0::DeployAccount(DeployAccountTransaction::V1( + DeployAccountTransactionV0V1 { + contract_address, + transaction_hash: _, + max_fee, + signature, + nonce, + contract_address_salt, + constructor_calldata, + class_hash, + }, + ))) => TransactionVariant::DeployAccountV0V1( + pathfinder_common::transaction::DeployAccountTransactionV0V1 { + contract_address, + max_fee, + version: TransactionVersion::ONE, signature, nonce, contract_address_salt, @@ -1297,7 +1432,7 @@ pub(crate) mod dto { class_hash, }, ), - Transaction::DeployAccount(DeployAccountTransaction::V3( + Transaction::V0(TransactionV0::DeployAccount(DeployAccountTransaction::V3( DeployAccountTransactionV3 { nonce, nonce_data_availability_mode, @@ -1308,12 +1443,11 @@ pub(crate) mod dto { sender_address, signature, transaction_hash: _, - version: _, contract_address_salt, constructor_calldata, class_hash, }, - )) => TransactionVariant::DeployAccountV3( + ))) => TransactionVariant::DeployAccountV3( pathfinder_common::transaction::DeployAccountTransactionV3 { contract_address: sender_address, signature, @@ -1328,15 +1462,17 @@ pub(crate) mod dto { class_hash, }, ), - Transaction::Invoke(InvokeTransaction::V0(InvokeTransactionV0 { - calldata, - sender_address, - entry_point_selector, - entry_point_type, - max_fee, - signature, - transaction_hash: _, - })) => TransactionVariant::InvokeV0( + Transaction::V0(TransactionV0::Invoke(InvokeTransaction::V0( + InvokeTransactionV0 { + calldata, + sender_address, + entry_point_selector, + entry_point_type, + max_fee, + signature, + transaction_hash: _, + }, + ))) => TransactionVariant::InvokeV0( pathfinder_common::transaction::InvokeTransactionV0 { calldata, sender_address, @@ -1346,14 +1482,16 @@ pub(crate) mod dto { signature, }, ), - Transaction::Invoke(InvokeTransaction::V1(InvokeTransactionV1 { - calldata, - sender_address, - max_fee, - signature, - nonce, - transaction_hash: _, - })) => TransactionVariant::InvokeV1( + Transaction::V0(TransactionV0::Invoke(InvokeTransaction::V1( + InvokeTransactionV1 { + calldata, + sender_address, + max_fee, + signature, + nonce, + transaction_hash: _, + }, + ))) => TransactionVariant::InvokeV1( pathfinder_common::transaction::InvokeTransactionV1 { calldata, sender_address, @@ -1362,19 +1500,21 @@ pub(crate) mod dto { nonce, }, ), - Transaction::Invoke(InvokeTransaction::V3(InvokeTransactionV3 { - nonce, - nonce_data_availability_mode, - fee_data_availability_mode, - resource_bounds, - tip, - paymaster_data, - sender_address, - signature, - transaction_hash: _, - calldata, - account_deployment_data, - })) => TransactionVariant::InvokeV3( + Transaction::V0(TransactionV0::Invoke(InvokeTransaction::V3( + InvokeTransactionV3 { + nonce, + nonce_data_availability_mode, + fee_data_availability_mode, + resource_bounds, + tip, + paymaster_data, + sender_address, + signature, + transaction_hash: _, + calldata, + account_deployment_data, + }, + ))) => TransactionVariant::InvokeV3( pathfinder_common::transaction::InvokeTransactionV3 { signature, nonce, @@ -1388,7 +1528,7 @@ pub(crate) mod dto { sender_address, }, ), - Transaction::L1Handler(L1HandlerTransaction { + Transaction::V0(TransactionV0::L1Handler(L1HandlerTransaction { contract_address, entry_point_selector, nonce, @@ -1396,7 +1536,7 @@ pub(crate) mod dto { transaction_hash: _, // This should always be zero. version: _, - }) => TransactionVariant::L1Handler( + })) => TransactionVariant::L1Handler( pathfinder_common::transaction::L1HandlerTransaction { contract_address, entry_point_selector, @@ -1414,67 +1554,94 @@ pub(crate) mod dto { /// Returns hash of the transaction pub fn hash(&self) -> TransactionHash { match self { - Transaction::Declare(t) => match t { + Transaction::V0(TransactionV0::Declare(t)) => match t { DeclareTransaction::V0(t) => t.transaction_hash, DeclareTransaction::V1(t) => t.transaction_hash, DeclareTransaction::V2(t) => t.transaction_hash, DeclareTransaction::V3(t) => t.transaction_hash, }, - Transaction::Deploy(t) => t.transaction_hash, - Transaction::DeployAccount(t) => match t { - DeployAccountTransaction::V0V1(t) => t.transaction_hash, + Transaction::V0(TransactionV0::Deploy(t)) => t.transaction_hash, + Transaction::V0(TransactionV0::DeployAccount(t)) => match t { + DeployAccountTransaction::V0(t) | DeployAccountTransaction::V1(t) => { + t.transaction_hash + } DeployAccountTransaction::V3(t) => t.transaction_hash, }, - Transaction::Invoke(t) => match t { + Transaction::V0(TransactionV0::Invoke(t)) => match t { InvokeTransaction::V0(t) => t.transaction_hash, InvokeTransaction::V1(t) => t.transaction_hash, InvokeTransaction::V3(t) => t.transaction_hash, }, - Transaction::L1Handler(t) => t.transaction_hash, + Transaction::V0(TransactionV0::L1Handler(t)) => t.transaction_hash, } } pub fn contract_address(&self) -> ContractAddress { match self { - Transaction::Declare(DeclareTransaction::V0(t)) => t.sender_address, - Transaction::Declare(DeclareTransaction::V1(t)) => t.sender_address, - Transaction::Declare(DeclareTransaction::V2(t)) => t.sender_address, - Transaction::Declare(DeclareTransaction::V3(t)) => t.sender_address, - Transaction::Deploy(t) => t.contract_address, - Transaction::DeployAccount(t) => match t { - DeployAccountTransaction::V0V1(t) => t.contract_address, + Transaction::V0(TransactionV0::Declare(DeclareTransaction::V0(t))) => { + t.sender_address + } + Transaction::V0(TransactionV0::Declare(DeclareTransaction::V1(t))) => { + t.sender_address + } + Transaction::V0(TransactionV0::Declare(DeclareTransaction::V2(t))) => { + t.sender_address + } + Transaction::V0(TransactionV0::Declare(DeclareTransaction::V3(t))) => { + t.sender_address + } + Transaction::V0(TransactionV0::Deploy(t)) => t.contract_address, + Transaction::V0(TransactionV0::DeployAccount(t)) => match t { + DeployAccountTransaction::V0(t) | DeployAccountTransaction::V1(t) => { + t.contract_address + } DeployAccountTransaction::V3(t) => t.sender_address, }, - Transaction::Invoke(t) => match t { + Transaction::V0(TransactionV0::Invoke(t)) => match t { InvokeTransaction::V0(t) => t.sender_address, InvokeTransaction::V1(t) => t.sender_address, InvokeTransaction::V3(t) => t.sender_address, }, - Transaction::L1Handler(t) => t.contract_address, + Transaction::V0(TransactionV0::L1Handler(t)) => t.contract_address, } } pub fn version(&self) -> TransactionVersion { match self { - Transaction::Declare(DeclareTransaction::V0(_)) => TransactionVersion::ZERO, - Transaction::Declare(DeclareTransaction::V1(_)) => TransactionVersion::ONE, - Transaction::Declare(DeclareTransaction::V2(_)) => TransactionVersion::TWO, - Transaction::Declare(DeclareTransaction::V3(_)) => TransactionVersion::THREE, - - Transaction::Deploy(t) => t.version, - Transaction::DeployAccount(t) => match t { - DeployAccountTransaction::V0V1(t) => t.version, - DeployAccountTransaction::V3(t) => t.version, + Transaction::V0(TransactionV0::Declare(DeclareTransaction::V0(_))) => { + TransactionVersion::ZERO + } + Transaction::V0(TransactionV0::Declare(DeclareTransaction::V1(_))) => { + TransactionVersion::ONE + } + Transaction::V0(TransactionV0::Declare(DeclareTransaction::V2(_))) => { + TransactionVersion::TWO + } + Transaction::V0(TransactionV0::Declare(DeclareTransaction::V3(_))) => { + TransactionVersion::THREE + } + + Transaction::V0(TransactionV0::Deploy(t)) => t.version, + Transaction::V0(TransactionV0::DeployAccount(t)) => match t { + DeployAccountTransaction::V0(..) => TransactionVersion::ZERO, + DeployAccountTransaction::V1(..) => TransactionVersion::ONE, + DeployAccountTransaction::V3(..) => TransactionVersion::THREE, }, - Transaction::Invoke(InvokeTransaction::V0(_)) => TransactionVersion::ZERO, - Transaction::Invoke(InvokeTransaction::V1(_)) => TransactionVersion::ONE, - Transaction::Invoke(InvokeTransaction::V3(_)) => TransactionVersion::THREE, - Transaction::L1Handler(t) => t.version, + Transaction::V0(TransactionV0::Invoke(InvokeTransaction::V0(_))) => { + TransactionVersion::ZERO + } + Transaction::V0(TransactionV0::Invoke(InvokeTransaction::V1(_))) => { + TransactionVersion::ONE + } + Transaction::V0(TransactionV0::Invoke(InvokeTransaction::V3(_))) => { + TransactionVersion::THREE + } + Transaction::V0(TransactionV0::L1Handler(t)) => t.version, } } } - #[derive(Clone, Debug, Serialize, PartialEq, Eq)] + #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(tag = "version")] pub enum DeclareTransaction { #[serde(rename = "0x0")] @@ -1487,44 +1654,6 @@ pub(crate) mod dto { V3(DeclareTransactionV3), } - impl<'de> Deserialize<'de> for DeclareTransaction { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - use serde::de; - - #[serde_as] - #[derive(Deserialize)] - struct Version { - #[serde(default = "transaction_version_zero")] - pub version: TransactionVersion, - } - - let mut v = serde_json::Value::deserialize(deserializer)?; - let version = Version::deserialize(&v).map_err(de::Error::custom)?; - // remove "version", since v0 and v1 transactions use deny_unknown_fields - v.as_object_mut() - .expect("must be an object because deserializing version succeeded") - .remove("version"); - match version.version { - TransactionVersion::ZERO => Ok(Self::V0( - DeclareTransactionV0V1::deserialize(&v).map_err(de::Error::custom)?, - )), - TransactionVersion::ONE => Ok(Self::V1( - DeclareTransactionV0V1::deserialize(&v).map_err(de::Error::custom)?, - )), - TransactionVersion::TWO => Ok(Self::V2( - DeclareTransactionV2::deserialize(&v).map_err(de::Error::custom)?, - )), - TransactionVersion::THREE => Ok(Self::V3( - DeclareTransactionV3::deserialize(&v).map_err(de::Error::custom)?, - )), - _v => Err(de::Error::custom("version must be 0, 1, 2 or 3")), - } - } - } - impl DeclareTransaction { pub fn signature(&self) -> &[TransactionSignatureElem] { match self { @@ -1662,56 +1791,28 @@ pub(crate) mod dto { } /// Represents deserialized L2 deploy account transaction data. - #[derive(Clone, Debug, Serialize, PartialEq, Eq, Dummy)] - #[serde(untagged)] + #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Dummy)] + #[serde(tag = "version")] pub enum DeployAccountTransaction { - V0V1(DeployAccountTransactionV0V1), + #[serde(rename = "0x0")] + V0(DeployAccountTransactionV0V1), + #[serde(rename = "0x1")] + V1(DeployAccountTransactionV0V1), + #[serde(rename = "0x3")] V3(DeployAccountTransactionV3), } - impl<'de> Deserialize<'de> for DeployAccountTransaction { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - use serde::de; - - #[serde_as] - #[derive(Deserialize)] - struct Version { - #[serde(default = "transaction_version_zero")] - pub version: TransactionVersion, - } - - let v = serde_json::Value::deserialize(deserializer)?; - let version = Version::deserialize(&v).map_err(de::Error::custom)?; - - match version.version { - TransactionVersion::ZERO => Ok(Self::V0V1( - DeployAccountTransactionV0V1::deserialize(&v).map_err(de::Error::custom)?, - )), - TransactionVersion::ONE => Ok(Self::V0V1( - DeployAccountTransactionV0V1::deserialize(&v).map_err(de::Error::custom)?, - )), - TransactionVersion::THREE => Ok(Self::V3( - DeployAccountTransactionV3::deserialize(&v).map_err(de::Error::custom)?, - )), - _v => Err(de::Error::custom("version must be 0, 1 or 3")), - } - } - } - impl DeployAccountTransaction { pub fn contract_address(&self) -> ContractAddress { match self { - Self::V0V1(tx) => tx.contract_address, + Self::V0(tx) | Self::V1(tx) => tx.contract_address, Self::V3(tx) => tx.sender_address, } } pub fn signature(&self) -> &[TransactionSignatureElem] { match self { - Self::V0V1(tx) => tx.signature.as_ref(), + Self::V0(tx) | Self::V1(tx) => tx.signature.as_ref(), Self::V3(tx) => tx.signature.as_ref(), } } @@ -1724,7 +1825,6 @@ pub(crate) mod dto { pub contract_address: ContractAddress, pub transaction_hash: TransactionHash, pub max_fee: Fee, - pub version: TransactionVersion, #[serde_as(as = "Vec")] pub signature: Vec, pub nonce: TransactionNonce, @@ -1741,7 +1841,6 @@ pub(crate) mod dto { let class_hash = Faker.fake_with_rng(rng); Self { - version: TransactionVersion::ONE, contract_address: ContractAddress::deployed_contract_address( constructor_calldata.iter().copied(), &contract_address_salt, @@ -1774,7 +1873,6 @@ pub(crate) mod dto { #[serde_as(as = "Vec")] pub signature: Vec, pub transaction_hash: TransactionHash, - pub version: TransactionVersion, pub contract_address_salt: ContractAddressSalt, #[serde_as(as = "Vec")] pub constructor_calldata: Vec, @@ -1802,7 +1900,6 @@ pub(crate) mod dto { ), signature: Faker.fake_with_rng(rng), transaction_hash: Faker.fake_with_rng(rng), - version: TransactionVersion::THREE, contract_address_salt, constructor_calldata, class_hash, @@ -1810,8 +1907,9 @@ pub(crate) mod dto { } } - #[derive(Clone, Debug, Serialize, PartialEq, Eq, Dummy)] + #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Dummy)] #[serde(tag = "version")] + #[serde(deny_unknown_fields)] pub enum InvokeTransaction { #[serde(rename = "0x0")] V0(InvokeTransactionV0), @@ -1821,41 +1919,6 @@ pub(crate) mod dto { V3(InvokeTransactionV3), } - impl<'de> Deserialize<'de> for InvokeTransaction { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - use serde::de; - - #[serde_as] - #[derive(Deserialize)] - struct Version { - #[serde(default = "transaction_version_zero")] - pub version: TransactionVersion, - } - - let mut v = serde_json::Value::deserialize(deserializer)?; - let version = Version::deserialize(&v).map_err(de::Error::custom)?; - // remove "version", since v0 and v1 transactions use deny_unknown_fields - v.as_object_mut() - .expect("must be an object because deserializing version succeeded") - .remove("version"); - match version.version { - TransactionVersion::ZERO => Ok(Self::V0( - InvokeTransactionV0::deserialize(&v).map_err(de::Error::custom)?, - )), - TransactionVersion::ONE => Ok(Self::V1( - InvokeTransactionV1::deserialize(&v).map_err(de::Error::custom)?, - )), - TransactionVersion::THREE => Ok(Self::V3( - InvokeTransactionV3::deserialize(&v).map_err(de::Error::custom)?, - )), - _v => Err(de::Error::custom("version must be 0, 1 or 3")), - } - } - } - impl InvokeTransaction { pub fn signature(&self) -> &[TransactionSignatureElem] { match self { diff --git a/crates/storage/src/fake.rs b/crates/storage/src/fake.rs index c5680a3b6e..22ad3d382a 100644 --- a/crates/storage/src/fake.rs +++ b/crates/storage/src/fake.rs @@ -156,13 +156,15 @@ pub mod init { let t: common::Transaction = t.into(); let transaction_hash = t.hash; - let r: Receipt = crate::connection::transaction::dto::Receipt { - transaction_hash, - transaction_index: TransactionIndex::new_or_panic( - i.try_into().expect("u64 is at least as wide as usize"), - ), - ..Faker.fake_with_rng(rng) - } + let r: Receipt = crate::connection::transaction::dto::Receipt::V0( + crate::connection::transaction::dto::ReceiptV0 { + transaction_hash, + transaction_index: TransactionIndex::new_or_panic( + i.try_into().expect("u64 is at least as wide as usize"), + ), + ..Faker.fake_with_rng(rng) + }, + ) .into(); let e: Vec = fake_non_empty_with_rng(rng); (t, r, e) diff --git a/crates/storage/src/schema/revision_0051.rs b/crates/storage/src/schema/revision_0051.rs index 4186038df0..3c1ce47430 100644 --- a/crates/storage/src/schema/revision_0051.rs +++ b/crates/storage/src/schema/revision_0051.rs @@ -1,82 +1,7 @@ +use std::mem; + use anyhow::Context; -use pathfinder_common::{ - ContractAddress, EthereumAddress, Fee, L2ToL1MessagePayloadElem, TransactionHash, - TransactionIndex, -}; -use pathfinder_serde::{EthereumAddressAsHexStr, L2ToL1MessagePayloadElemAsDecimalStr}; use rusqlite::params; -use serde::{Deserialize, Serialize}; -use serde_with::serde_as; - -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct ReceiptDto { - #[serde(default)] - pub actual_fee: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub events: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub execution_resources: Option, - // This field exists in our database but is unused within our code. - // It is redundant data that is also contained in the L1 handler. - pub l1_to_l2_consumed_message: Option, - pub l2_to_l1_messages: Vec, - pub transaction_hash: TransactionHash, - pub transaction_index: TransactionIndex, - // Introduced in v0.12.1 - #[serde(default)] - pub execution_status: ExecutionStatus, - // Introduced in v0.12.1 - /// Only present if status is [ExecutionStatus::Reverted]. - #[serde(default)] - pub revert_error: Option, -} - -#[serde_as] -#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct L2ToL1Message { - pub from_address: ContractAddress, - #[serde_as(as = "Vec")] - pub payload: Vec, - #[serde_as(as = "EthereumAddressAsHexStr")] - pub to_address: EthereumAddress, -} - -#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)] -#[serde(deny_unknown_fields)] -pub struct ExecutionResources { - pub builtin_instance_counter: BuiltinCounters, - pub n_steps: u64, - pub n_memory_holes: u64, - // TODO make these mandatory once some new release makes resyncing necessary - pub l1_gas: Option, - pub l1_data_gas: Option, -} - -#[derive(Copy, Clone, Default, Debug, Deserialize, Serialize, PartialEq, Eq)] -#[serde(default)] -pub struct BuiltinCounters { - pub output_builtin: u64, - pub pedersen_builtin: u64, - pub range_check_builtin: u64, - pub ecdsa_builtin: u64, - pub bitwise_builtin: u64, - pub ec_op_builtin: u64, - pub keccak_builtin: u64, - pub poseidon_builtin: u64, - pub segment_arena_builtin: u64, -} - -#[derive(Clone, Default, Debug, Deserialize, Serialize, PartialEq, Eq)] -#[serde(rename_all = "SCREAMING_SNAKE_CASE")] -pub enum ExecutionStatus { - // This must be the default as pre v0.12.1 receipts did not contain this value and - // were always success as reverted did not exist. - #[default] - Succeeded, - Reverted, -} /// NOTE: DO NOT RUN THIS IN PROD. /// TODO: Update this to use bincode serialization instead of serde_json. (Follow-up PR) @@ -88,27 +13,1620 @@ ALTER TABLE starknet_transactions ADD COLUMN events BLOB DEFAULT NULL; [], ) .context("Altering starknet_transactions table: adding new column")?; - let mut stmt = tx.prepare("SELECT hash, receipt FROM starknet_transactions")?; + let mut stmt = tx.prepare("SELECT hash, receipt, tx FROM starknet_transactions")?; let mut rows = stmt.query([])?; let mut compressor = zstd::bulk::Compressor::new(10).context("Create zstd compressor")?; while let Some(row) = rows.next()? { - let hash = row.get_ref_unwrap(0).as_blob()?; - let receipt = row.get_ref_unwrap(1).as_blob()?; + let hash = row.get_ref_unwrap("hash").as_blob()?; + + // Load old DTOs. + let transaction = row.get_ref_unwrap("tx").as_blob()?; + let transaction = zstd::decode_all(transaction).context("Decompressing transaction")?; + let transaction: old_dto::Transaction = + serde_json::from_slice(&transaction).context("Deserializing transaction")?; + let transaction = pathfinder_common::transaction::Transaction::from(transaction); + let receipt = row.get_ref_unwrap("receipt").as_blob()?; let receipt = zstd::decode_all(receipt).context("Decompressing receipt")?; - let mut receipt: ReceiptDto = + let mut receipt: old_dto::Receipt = serde_json::from_slice(&receipt).context("Deserializing receipt")?; - let events = - serde_json::to_vec(&receipt.events.as_ref().unwrap()).context("Serializing events")?; - let events = compressor.compress(&events).context("Compressing events")?; - receipt.events = None; - let receipt = serde_json::to_vec(&receipt).context("Serializing receipt")?; + let events = mem::take(&mut receipt.events); + let receipt = pathfinder_common::receipt::Receipt::from(receipt); + + // Serialize into new DTOs. + let transaction = crate::transaction::dto::Transaction::from(&transaction); + let transaction = bincode::serde::encode_to_vec(transaction, bincode::config::standard()) + .context("Serializing transaction")?; + let transaction = compressor + .compress(&transaction) + .context("Compressing transaction")?; + let receipt = crate::transaction::dto::Receipt::from(&receipt); + let receipt = bincode::serde::encode_to_vec(receipt, bincode::config::standard()) + .context("Serializing receipt")?; let receipt = compressor .compress(&receipt) .context("Compressing receipt")?; + let events = bincode::serde::encode_to_vec( + crate::transaction::dto::Events::V0 { events }, + bincode::config::standard(), + ) + .context("Serializing events")?; + let events = compressor.compress(&events).context("Compressing events")?; + + // Store the updated values. tx.execute( - "UPDATE starknet_transactions SET receipt = ?, events = ? WHERE hash = ?", - params![receipt, events, hash], + "UPDATE starknet_transactions SET receipt = ?, events = ?, tx = ? WHERE hash = ?", + params![receipt, events, transaction, hash], )?; } Ok(()) } + +pub(crate) mod old_dto { + use fake::{Dummy, Fake, Faker}; + use pathfinder_common::*; + use pathfinder_crypto::Felt; + use pathfinder_serde::{ + CallParamAsDecimalStr, ConstructorParamAsDecimalStr, EthereumAddressAsHexStr, + L2ToL1MessagePayloadElemAsDecimalStr, ResourceAmountAsHexStr, ResourcePricePerUnitAsHexStr, + TipAsHexStr, TransactionSignatureElemAsDecimalStr, + }; + use serde::{Deserialize, Serialize}; + use serde_with::serde_as; + + /// Represents deserialized L2 transaction entry point values. + #[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] + #[serde(deny_unknown_fields)] + pub enum EntryPointType { + #[serde(rename = "EXTERNAL")] + External, + #[serde(rename = "L1_HANDLER")] + L1Handler, + } + + impl From for EntryPointType { + fn from(value: pathfinder_common::transaction::EntryPointType) -> Self { + use pathfinder_common::transaction::EntryPointType::{External, L1Handler}; + match value { + External => Self::External, + L1Handler => Self::L1Handler, + } + } + } + + impl From for pathfinder_common::transaction::EntryPointType { + fn from(value: EntryPointType) -> Self { + match value { + EntryPointType::External => Self::External, + EntryPointType::L1Handler => Self::L1Handler, + } + } + } + + /// Represents execution resources for L2 transaction. + #[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)] + #[serde(deny_unknown_fields)] + pub struct ExecutionResources { + pub builtin_instance_counter: BuiltinCounters, + pub n_steps: u64, + pub n_memory_holes: u64, + // TODO make these mandatory once some new release makes resyncing necessary + pub l1_gas: Option, + pub l1_data_gas: Option, + } + + impl From<&ExecutionResources> for pathfinder_common::receipt::ExecutionResources { + fn from(value: &ExecutionResources) -> Self { + Self { + builtin_instance_counter: value.builtin_instance_counter.into(), + n_steps: value.n_steps, + n_memory_holes: value.n_memory_holes, + data_availability: match (value.l1_gas, value.l1_data_gas) { + (Some(l1_gas), Some(l1_data_gas)) => { + pathfinder_common::receipt::ExecutionDataAvailability { + l1_gas, + l1_data_gas, + } + } + _ => Default::default(), + }, + } + } + } + + impl From<&pathfinder_common::receipt::ExecutionResources> for ExecutionResources { + fn from(value: &pathfinder_common::receipt::ExecutionResources) -> Self { + Self { + builtin_instance_counter: (&value.builtin_instance_counter).into(), + n_steps: value.n_steps, + n_memory_holes: value.n_memory_holes, + l1_gas: Some(value.data_availability.l1_gas), + l1_data_gas: Some(value.data_availability.l1_data_gas), + } + } + } + + impl Dummy for ExecutionResources { + fn dummy_with_rng(_: &T, rng: &mut R) -> Self { + let (l1_gas, l1_data_gas) = if rng.gen() { + (Some(rng.next_u32() as u128), Some(rng.next_u32() as u128)) + } else { + (None, None) + }; + + Self { + builtin_instance_counter: Faker.fake_with_rng(rng), + n_steps: rng.next_u32() as u64, + n_memory_holes: rng.next_u32() as u64, + l1_gas, + l1_data_gas, + } + } + } + + // This struct purposefully allows for unknown fields as it is not critical to + // store these counters perfectly. Failure would be far more costly than simply + // ignoring them. + #[derive(Copy, Clone, Default, Debug, Deserialize, Serialize, PartialEq, Eq)] + #[serde(default)] + pub struct BuiltinCounters { + pub output_builtin: u64, + pub pedersen_builtin: u64, + pub range_check_builtin: u64, + pub ecdsa_builtin: u64, + pub bitwise_builtin: u64, + pub ec_op_builtin: u64, + pub keccak_builtin: u64, + pub poseidon_builtin: u64, + pub segment_arena_builtin: u64, + } + + impl From for pathfinder_common::receipt::BuiltinCounters { + fn from(value: BuiltinCounters) -> Self { + // Use deconstruction to ensure these structs remain in-sync. + let BuiltinCounters { + output_builtin, + pedersen_builtin, + range_check_builtin, + ecdsa_builtin, + bitwise_builtin, + ec_op_builtin, + keccak_builtin, + poseidon_builtin, + segment_arena_builtin, + } = value; + Self { + output_builtin, + pedersen_builtin, + range_check_builtin, + ecdsa_builtin, + bitwise_builtin, + ec_op_builtin, + keccak_builtin, + poseidon_builtin, + segment_arena_builtin, + } + } + } + + impl From<&pathfinder_common::receipt::BuiltinCounters> for BuiltinCounters { + fn from(value: &pathfinder_common::receipt::BuiltinCounters) -> Self { + // Use deconstruction to ensure these structs remain in-sync. + let pathfinder_common::receipt::BuiltinCounters { + output_builtin, + pedersen_builtin, + range_check_builtin, + ecdsa_builtin, + bitwise_builtin, + ec_op_builtin, + keccak_builtin, + poseidon_builtin, + segment_arena_builtin, + } = value.clone(); + Self { + output_builtin, + pedersen_builtin, + range_check_builtin, + ecdsa_builtin, + bitwise_builtin, + ec_op_builtin, + keccak_builtin, + poseidon_builtin, + segment_arena_builtin, + } + } + } + + impl Dummy for BuiltinCounters { + fn dummy_with_rng(_: &T, rng: &mut R) -> Self { + Self { + output_builtin: rng.next_u32() as u64, + pedersen_builtin: rng.next_u32() as u64, + range_check_builtin: rng.next_u32() as u64, + ecdsa_builtin: rng.next_u32() as u64, + bitwise_builtin: rng.next_u32() as u64, + ec_op_builtin: rng.next_u32() as u64, + keccak_builtin: rng.next_u32() as u64, + poseidon_builtin: rng.next_u32() as u64, + segment_arena_builtin: 0, // Not used in p2p + } + } + } + + /// Represents deserialized L2 to L1 message. + #[serde_as] + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] + #[serde(deny_unknown_fields)] + pub struct L2ToL1Message { + pub from_address: ContractAddress, + #[serde_as(as = "Vec")] + pub payload: Vec, + #[serde_as(as = "EthereumAddressAsHexStr")] + pub to_address: EthereumAddress, + } + + impl From for pathfinder_common::receipt::L2ToL1Message { + fn from(value: L2ToL1Message) -> Self { + let L2ToL1Message { + from_address, + payload, + to_address, + } = value; + pathfinder_common::receipt::L2ToL1Message { + from_address, + payload, + to_address, + } + } + } + + impl From<&pathfinder_common::receipt::L2ToL1Message> for L2ToL1Message { + fn from(value: &pathfinder_common::receipt::L2ToL1Message) -> Self { + let pathfinder_common::receipt::L2ToL1Message { + from_address, + payload, + to_address, + } = value.clone(); + Self { + from_address, + payload, + to_address, + } + } + } + + #[derive(Clone, Default, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] + #[serde(rename_all = "SCREAMING_SNAKE_CASE")] + pub enum ExecutionStatus { + // This must be the default as pre v0.12.1 receipts did not contain this value and + // were always success as reverted did not exist. + #[default] + Succeeded, + Reverted, + } + + /// Represents deserialized L2 transaction receipt data. + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] + #[serde(deny_unknown_fields)] + pub struct Receipt { + #[serde(default)] + pub actual_fee: Option, + pub events: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub execution_resources: Option, + // This field exists in our database but is unused within our code. + // It is redundant data that is also contained in the L1 handler. + pub l1_to_l2_consumed_message: Option, + pub l2_to_l1_messages: Vec, + pub transaction_hash: TransactionHash, + pub transaction_index: TransactionIndex, + // Introduced in v0.12.1 + #[serde(default)] + pub execution_status: ExecutionStatus, + // Introduced in v0.12.1 + /// Only present if status is [ExecutionStatus::Reverted]. + #[serde(default)] + pub revert_error: Option, + } + + impl From<&pathfinder_common::receipt::Receipt> for Receipt { + fn from(value: &pathfinder_common::receipt::Receipt) -> Self { + let (execution_status, revert_error) = match &value.execution_status { + receipt::ExecutionStatus::Succeeded => (ExecutionStatus::Succeeded, None), + receipt::ExecutionStatus::Reverted { reason } => { + (ExecutionStatus::Reverted, Some(reason.clone())) + } + }; + + Self { + actual_fee: value.actual_fee, + events: vec![], + execution_resources: Some((&value.execution_resources).into()), + // We don't care about this field anymore. + l1_to_l2_consumed_message: None, + l2_to_l1_messages: value.l2_to_l1_messages.iter().map(Into::into).collect(), + transaction_hash: value.transaction_hash, + transaction_index: value.transaction_index, + execution_status, + revert_error, + } + } + } + + impl From for pathfinder_common::receipt::Receipt { + fn from(value: Receipt) -> Self { + use pathfinder_common::receipt as common; + + let Receipt { + actual_fee, + events: _, + execution_resources, + // This information is redundant as it is already in the transaction itself. + l1_to_l2_consumed_message: _, + l2_to_l1_messages, + transaction_hash, + transaction_index, + execution_status, + revert_error, + } = value; + + common::Receipt { + actual_fee, + execution_resources: (&execution_resources.unwrap_or_default()).into(), + l2_to_l1_messages: l2_to_l1_messages.into_iter().map(Into::into).collect(), + transaction_hash, + transaction_index, + execution_status: match execution_status { + ExecutionStatus::Succeeded => common::ExecutionStatus::Succeeded, + ExecutionStatus::Reverted => common::ExecutionStatus::Reverted { + reason: revert_error.unwrap_or_default(), + }, + }, + } + } + } + + impl Dummy for Receipt { + fn dummy_with_rng(_: &T, rng: &mut R) -> Self { + let execution_status = Faker.fake_with_rng(rng); + let revert_error = + (execution_status == ExecutionStatus::Reverted).then(|| Faker.fake_with_rng(rng)); + + // Those fields that were missing in very old receipts are always present + Self { + actual_fee: Some(Faker.fake_with_rng(rng)), + execution_resources: Some(Faker.fake_with_rng(rng)), + events: Faker.fake_with_rng(rng), + l1_to_l2_consumed_message: Faker.fake_with_rng(rng), + l2_to_l1_messages: Faker.fake_with_rng(rng), + transaction_hash: Faker.fake_with_rng(rng), + transaction_index: Faker.fake_with_rng(rng), + execution_status, + revert_error, + } + } + } + + #[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Dummy)] + pub enum DataAvailabilityMode { + #[default] + L1, + L2, + } + + impl Serialize for DataAvailabilityMode { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + DataAvailabilityMode::L1 => serializer.serialize_u8(0), + DataAvailabilityMode::L2 => serializer.serialize_u8(1), + } + } + } + + impl<'de> Deserialize<'de> for DataAvailabilityMode { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + match ::deserialize(deserializer)? { + 0 => Ok(Self::L1), + 1 => Ok(Self::L2), + _ => Err(serde::de::Error::custom("invalid data availability mode")), + } + } + } + + impl From for pathfinder_common::transaction::DataAvailabilityMode { + fn from(value: DataAvailabilityMode) -> Self { + match value { + DataAvailabilityMode::L1 => Self::L1, + DataAvailabilityMode::L2 => Self::L2, + } + } + } + + impl From for DataAvailabilityMode { + fn from(value: pathfinder_common::transaction::DataAvailabilityMode) -> Self { + match value { + pathfinder_common::transaction::DataAvailabilityMode::L1 => Self::L1, + pathfinder_common::transaction::DataAvailabilityMode::L2 => Self::L2, + } + } + } + + #[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq, Dummy)] + pub struct ResourceBounds { + #[serde(rename = "L1_GAS")] + pub l1_gas: ResourceBound, + #[serde(rename = "L2_GAS")] + pub l2_gas: ResourceBound, + } + + impl From for pathfinder_common::transaction::ResourceBounds { + fn from(value: ResourceBounds) -> Self { + Self { + l1_gas: value.l1_gas.into(), + l2_gas: value.l2_gas.into(), + } + } + } + + impl From for ResourceBounds { + fn from(value: pathfinder_common::transaction::ResourceBounds) -> Self { + Self { + l1_gas: value.l1_gas.into(), + l2_gas: value.l2_gas.into(), + } + } + } + + #[serde_as] + #[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq, Dummy)] + pub struct ResourceBound { + #[serde_as(as = "ResourceAmountAsHexStr")] + pub max_amount: ResourceAmount, + #[serde_as(as = "ResourcePricePerUnitAsHexStr")] + pub max_price_per_unit: ResourcePricePerUnit, + } + + impl From for pathfinder_common::transaction::ResourceBound { + fn from(value: ResourceBound) -> Self { + Self { + max_amount: value.max_amount, + max_price_per_unit: value.max_price_per_unit, + } + } + } + + impl From for ResourceBound { + fn from(value: pathfinder_common::transaction::ResourceBound) -> Self { + Self { + max_amount: value.max_amount, + max_price_per_unit: value.max_price_per_unit, + } + } + } + + /// Represents deserialized L2 transaction data. + #[derive(Clone, Debug, Serialize, PartialEq, Eq, Dummy)] + #[serde(tag = "type")] + #[serde(deny_unknown_fields)] + pub enum Transaction { + #[serde(rename = "DECLARE")] + Declare(DeclareTransaction), + #[serde(rename = "DEPLOY")] + // FIXME regenesis: remove Deploy txn type after regenesis + // We are keeping this type of transaction until regenesis + // only to support older pre-0.11.0 blocks + Deploy(DeployTransaction), + #[serde(rename = "DEPLOY_ACCOUNT")] + DeployAccount(DeployAccountTransaction), + #[serde(rename = "INVOKE_FUNCTION")] + Invoke(InvokeTransaction), + #[serde(rename = "L1_HANDLER")] + L1Handler(L1HandlerTransaction), + } + + // This manual deserializtion is a work-around for L1 handler transactions + // historically being served as Invoke V0. However, the gateway has retroactively + // changed these to L1 handlers. This means older databases will have these as Invoke + // but modern one's as L1 handler. This causes confusion, so we convert these old Invoke + // to L1 handler manually. + // + // The alternative is to do a costly database migration which involves opening every tx. + // + // This work-around may be removed once we are certain all databases no longer contain these + // transactions, which will likely only occur after either a migration, or regenesis. + impl<'de> Deserialize<'de> for Transaction { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + /// Copy of [Transaction] to deserialize into, before converting to [Transaction] + /// with the potential Invoke V0 -> L1 handler cast. + #[derive(Deserialize)] + #[serde(tag = "type", deny_unknown_fields)] + pub enum InnerTransaction { + #[serde(rename = "DECLARE")] + Declare(DeclareTransaction), + #[serde(rename = "DEPLOY")] + Deploy(DeployTransaction), + #[serde(rename = "DEPLOY_ACCOUNT")] + DeployAccount(DeployAccountTransaction), + #[serde(rename = "INVOKE_FUNCTION")] + Invoke(InvokeTransaction), + #[serde(rename = "L1_HANDLER")] + L1Handler(L1HandlerTransaction), + } + + let tx = InnerTransaction::deserialize(deserializer)?; + let tx = match tx { + InnerTransaction::Declare(x) => Transaction::Declare(x), + InnerTransaction::Deploy(x) => Transaction::Deploy(x), + InnerTransaction::DeployAccount(x) => Transaction::DeployAccount(x), + InnerTransaction::Invoke(InvokeTransaction::V0(i)) + if i.entry_point_type == Some(EntryPointType::L1Handler) => + { + let l1_handler = L1HandlerTransaction { + contract_address: i.sender_address, + entry_point_selector: i.entry_point_selector, + nonce: TransactionNonce::ZERO, + calldata: i.calldata, + transaction_hash: i.transaction_hash, + version: TransactionVersion::ZERO, + }; + + Transaction::L1Handler(l1_handler) + } + InnerTransaction::Invoke(x) => Transaction::Invoke(x), + InnerTransaction::L1Handler(x) => Transaction::L1Handler(x), + }; + + Ok(tx) + } + } + + impl From<&pathfinder_common::transaction::Transaction> for Transaction { + fn from(value: &pathfinder_common::transaction::Transaction) -> Self { + use pathfinder_common::transaction::TransactionVariant::*; + use pathfinder_common::transaction::*; + + let transaction_hash = value.hash; + match value.variant.clone() { + DeclareV0(DeclareTransactionV0V1 { + class_hash, + max_fee, + nonce, + sender_address, + signature, + }) => Self::Declare(DeclareTransaction::V0(self::DeclareTransactionV0V1 { + class_hash, + max_fee, + nonce, + sender_address, + signature, + transaction_hash, + })), + DeclareV1(DeclareTransactionV0V1 { + class_hash, + max_fee, + nonce, + sender_address, + signature, + }) => Self::Declare(DeclareTransaction::V1(self::DeclareTransactionV0V1 { + class_hash, + max_fee, + nonce, + sender_address, + signature, + transaction_hash, + })), + DeclareV2(DeclareTransactionV2 { + class_hash, + max_fee, + nonce, + sender_address, + signature, + compiled_class_hash, + }) => Self::Declare(DeclareTransaction::V2(self::DeclareTransactionV2 { + class_hash, + max_fee, + nonce, + sender_address, + signature, + transaction_hash, + compiled_class_hash, + })), + DeclareV3(DeclareTransactionV3 { + class_hash, + nonce, + nonce_data_availability_mode, + fee_data_availability_mode, + resource_bounds, + tip, + paymaster_data, + signature, + account_deployment_data, + sender_address, + compiled_class_hash, + }) => Self::Declare(DeclareTransaction::V3(self::DeclareTransactionV3 { + class_hash, + nonce, + nonce_data_availability_mode: nonce_data_availability_mode.into(), + fee_data_availability_mode: fee_data_availability_mode.into(), + resource_bounds: resource_bounds.into(), + tip, + paymaster_data, + sender_address, + signature, + transaction_hash, + compiled_class_hash, + account_deployment_data, + })), + Deploy(DeployTransaction { + contract_address, + contract_address_salt, + class_hash, + constructor_calldata, + version, + }) => Self::Deploy(self::DeployTransaction { + contract_address, + contract_address_salt, + class_hash, + constructor_calldata, + transaction_hash, + version, + }), + DeployAccountV0V1(DeployAccountTransactionV0V1 { + contract_address, + max_fee, + version, + signature, + nonce, + contract_address_salt, + constructor_calldata, + class_hash, + }) => Self::DeployAccount(self::DeployAccountTransaction::V0V1( + self::DeployAccountTransactionV0V1 { + contract_address, + transaction_hash, + max_fee, + version, + signature, + nonce, + contract_address_salt, + constructor_calldata, + class_hash, + }, + )), + DeployAccountV3(DeployAccountTransactionV3 { + contract_address, + signature, + nonce, + nonce_data_availability_mode, + fee_data_availability_mode, + resource_bounds, + tip, + paymaster_data, + contract_address_salt, + constructor_calldata, + class_hash, + }) => Self::DeployAccount(self::DeployAccountTransaction::V3( + self::DeployAccountTransactionV3 { + nonce, + nonce_data_availability_mode: nonce_data_availability_mode.into(), + fee_data_availability_mode: fee_data_availability_mode.into(), + resource_bounds: resource_bounds.into(), + tip, + paymaster_data, + sender_address: contract_address, + signature, + transaction_hash, + version: TransactionVersion::THREE, + contract_address_salt, + constructor_calldata, + class_hash, + }, + )), + InvokeV0(InvokeTransactionV0 { + calldata, + sender_address, + entry_point_selector, + entry_point_type, + max_fee, + signature, + }) => Self::Invoke(InvokeTransaction::V0(self::InvokeTransactionV0 { + calldata, + sender_address, + entry_point_selector, + entry_point_type: entry_point_type.map(Into::into), + max_fee, + signature, + transaction_hash, + })), + InvokeV1(InvokeTransactionV1 { + calldata, + sender_address, + max_fee, + signature, + nonce, + }) => Self::Invoke(InvokeTransaction::V1(self::InvokeTransactionV1 { + calldata, + sender_address, + max_fee, + signature, + nonce, + transaction_hash, + })), + InvokeV3(InvokeTransactionV3 { + signature, + nonce, + nonce_data_availability_mode, + fee_data_availability_mode, + resource_bounds, + tip, + paymaster_data, + account_deployment_data, + calldata, + sender_address, + }) => Self::Invoke(InvokeTransaction::V3(self::InvokeTransactionV3 { + nonce, + nonce_data_availability_mode: nonce_data_availability_mode.into(), + fee_data_availability_mode: fee_data_availability_mode.into(), + resource_bounds: resource_bounds.into(), + tip, + paymaster_data, + sender_address, + signature, + transaction_hash, + calldata, + account_deployment_data, + })), + L1Handler(L1HandlerTransaction { + contract_address, + entry_point_selector, + nonce, + calldata, + }) => Self::L1Handler(self::L1HandlerTransaction { + contract_address, + entry_point_selector, + nonce, + calldata, + transaction_hash, + version: TransactionVersion::ZERO, + }), + } + } + } + + impl From for pathfinder_common::transaction::Transaction { + fn from(value: Transaction) -> Self { + use pathfinder_common::transaction::TransactionVariant; + + let hash = value.hash(); + let variant = match value { + Transaction::Declare(DeclareTransaction::V0(DeclareTransactionV0V1 { + class_hash, + max_fee, + nonce, + sender_address, + signature, + transaction_hash: _, + })) => TransactionVariant::DeclareV0( + pathfinder_common::transaction::DeclareTransactionV0V1 { + class_hash, + max_fee, + nonce, + sender_address, + signature, + }, + ), + Transaction::Declare(DeclareTransaction::V1(DeclareTransactionV0V1 { + class_hash, + max_fee, + nonce, + sender_address, + signature, + transaction_hash: _, + })) => TransactionVariant::DeclareV1( + pathfinder_common::transaction::DeclareTransactionV0V1 { + class_hash, + max_fee, + nonce, + sender_address, + signature, + }, + ), + Transaction::Declare(DeclareTransaction::V2(DeclareTransactionV2 { + class_hash, + max_fee, + nonce, + sender_address, + signature, + transaction_hash: _, + compiled_class_hash, + })) => TransactionVariant::DeclareV2( + pathfinder_common::transaction::DeclareTransactionV2 { + class_hash, + max_fee, + nonce, + sender_address, + signature, + compiled_class_hash, + }, + ), + Transaction::Declare(DeclareTransaction::V3(DeclareTransactionV3 { + class_hash, + nonce, + nonce_data_availability_mode, + fee_data_availability_mode, + resource_bounds, + tip, + paymaster_data, + sender_address, + signature, + transaction_hash: _, + compiled_class_hash, + account_deployment_data, + })) => TransactionVariant::DeclareV3( + pathfinder_common::transaction::DeclareTransactionV3 { + class_hash, + nonce, + nonce_data_availability_mode: nonce_data_availability_mode.into(), + fee_data_availability_mode: fee_data_availability_mode.into(), + resource_bounds: resource_bounds.into(), + tip, + paymaster_data, + sender_address, + signature, + compiled_class_hash, + account_deployment_data, + }, + ), + Transaction::Deploy(DeployTransaction { + contract_address, + contract_address_salt, + class_hash, + constructor_calldata, + transaction_hash: _, + version, + }) => { + TransactionVariant::Deploy(pathfinder_common::transaction::DeployTransaction { + contract_address, + contract_address_salt, + class_hash, + constructor_calldata, + version, + }) + } + Transaction::DeployAccount(DeployAccountTransaction::V0V1( + DeployAccountTransactionV0V1 { + contract_address, + transaction_hash: _, + max_fee, + version, + signature, + nonce, + contract_address_salt, + constructor_calldata, + class_hash, + }, + )) => TransactionVariant::DeployAccountV0V1( + pathfinder_common::transaction::DeployAccountTransactionV0V1 { + contract_address, + max_fee, + version, + signature, + nonce, + contract_address_salt, + constructor_calldata, + class_hash, + }, + ), + Transaction::DeployAccount(DeployAccountTransaction::V3( + DeployAccountTransactionV3 { + nonce, + nonce_data_availability_mode, + fee_data_availability_mode, + resource_bounds, + tip, + paymaster_data, + sender_address, + signature, + transaction_hash: _, + version: _, + contract_address_salt, + constructor_calldata, + class_hash, + }, + )) => TransactionVariant::DeployAccountV3( + pathfinder_common::transaction::DeployAccountTransactionV3 { + contract_address: sender_address, + signature, + nonce, + nonce_data_availability_mode: nonce_data_availability_mode.into(), + fee_data_availability_mode: fee_data_availability_mode.into(), + resource_bounds: resource_bounds.into(), + tip, + paymaster_data, + contract_address_salt, + constructor_calldata, + class_hash, + }, + ), + Transaction::Invoke(InvokeTransaction::V0(InvokeTransactionV0 { + calldata, + sender_address, + entry_point_selector, + entry_point_type, + max_fee, + signature, + transaction_hash: _, + })) => TransactionVariant::InvokeV0( + pathfinder_common::transaction::InvokeTransactionV0 { + calldata, + sender_address, + entry_point_selector, + entry_point_type: entry_point_type.map(Into::into), + max_fee, + signature, + }, + ), + Transaction::Invoke(InvokeTransaction::V1(InvokeTransactionV1 { + calldata, + sender_address, + max_fee, + signature, + nonce, + transaction_hash: _, + })) => TransactionVariant::InvokeV1( + pathfinder_common::transaction::InvokeTransactionV1 { + calldata, + sender_address, + max_fee, + signature, + nonce, + }, + ), + Transaction::Invoke(InvokeTransaction::V3(InvokeTransactionV3 { + nonce, + nonce_data_availability_mode, + fee_data_availability_mode, + resource_bounds, + tip, + paymaster_data, + sender_address, + signature, + transaction_hash: _, + calldata, + account_deployment_data, + })) => TransactionVariant::InvokeV3( + pathfinder_common::transaction::InvokeTransactionV3 { + signature, + nonce, + nonce_data_availability_mode: nonce_data_availability_mode.into(), + fee_data_availability_mode: fee_data_availability_mode.into(), + resource_bounds: resource_bounds.into(), + tip, + paymaster_data, + account_deployment_data, + calldata, + sender_address, + }, + ), + Transaction::L1Handler(L1HandlerTransaction { + contract_address, + entry_point_selector, + nonce, + calldata, + transaction_hash: _, + // This should always be zero. + version: _, + }) => TransactionVariant::L1Handler( + pathfinder_common::transaction::L1HandlerTransaction { + contract_address, + entry_point_selector, + nonce, + calldata, + }, + ), + }; + + pathfinder_common::transaction::Transaction { hash, variant } + } + } + + impl Transaction { + /// Returns hash of the transaction + pub fn hash(&self) -> TransactionHash { + match self { + Transaction::Declare(t) => match t { + DeclareTransaction::V0(t) => t.transaction_hash, + DeclareTransaction::V1(t) => t.transaction_hash, + DeclareTransaction::V2(t) => t.transaction_hash, + DeclareTransaction::V3(t) => t.transaction_hash, + }, + Transaction::Deploy(t) => t.transaction_hash, + Transaction::DeployAccount(t) => match t { + DeployAccountTransaction::V0V1(t) => t.transaction_hash, + DeployAccountTransaction::V3(t) => t.transaction_hash, + }, + Transaction::Invoke(t) => match t { + InvokeTransaction::V0(t) => t.transaction_hash, + InvokeTransaction::V1(t) => t.transaction_hash, + InvokeTransaction::V3(t) => t.transaction_hash, + }, + Transaction::L1Handler(t) => t.transaction_hash, + } + } + + pub fn contract_address(&self) -> ContractAddress { + match self { + Transaction::Declare(DeclareTransaction::V0(t)) => t.sender_address, + Transaction::Declare(DeclareTransaction::V1(t)) => t.sender_address, + Transaction::Declare(DeclareTransaction::V2(t)) => t.sender_address, + Transaction::Declare(DeclareTransaction::V3(t)) => t.sender_address, + Transaction::Deploy(t) => t.contract_address, + Transaction::DeployAccount(t) => match t { + DeployAccountTransaction::V0V1(t) => t.contract_address, + DeployAccountTransaction::V3(t) => t.sender_address, + }, + Transaction::Invoke(t) => match t { + InvokeTransaction::V0(t) => t.sender_address, + InvokeTransaction::V1(t) => t.sender_address, + InvokeTransaction::V3(t) => t.sender_address, + }, + Transaction::L1Handler(t) => t.contract_address, + } + } + + pub fn version(&self) -> TransactionVersion { + match self { + Transaction::Declare(DeclareTransaction::V0(_)) => TransactionVersion::ZERO, + Transaction::Declare(DeclareTransaction::V1(_)) => TransactionVersion::ONE, + Transaction::Declare(DeclareTransaction::V2(_)) => TransactionVersion::TWO, + Transaction::Declare(DeclareTransaction::V3(_)) => TransactionVersion::THREE, + + Transaction::Deploy(t) => t.version, + Transaction::DeployAccount(t) => match t { + DeployAccountTransaction::V0V1(t) => t.version, + DeployAccountTransaction::V3(t) => t.version, + }, + Transaction::Invoke(InvokeTransaction::V0(_)) => TransactionVersion::ZERO, + Transaction::Invoke(InvokeTransaction::V1(_)) => TransactionVersion::ONE, + Transaction::Invoke(InvokeTransaction::V3(_)) => TransactionVersion::THREE, + Transaction::L1Handler(t) => t.version, + } + } + } + + #[derive(Clone, Debug, Serialize, PartialEq, Eq)] + #[serde(tag = "version")] + pub enum DeclareTransaction { + #[serde(rename = "0x0")] + V0(DeclareTransactionV0V1), + #[serde(rename = "0x1")] + V1(DeclareTransactionV0V1), + #[serde(rename = "0x2")] + V2(DeclareTransactionV2), + #[serde(rename = "0x3")] + V3(DeclareTransactionV3), + } + + impl<'de> Deserialize<'de> for DeclareTransaction { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use serde::de; + + #[serde_as] + #[derive(Deserialize)] + struct Version { + #[serde(default = "transaction_version_zero")] + pub version: TransactionVersion, + } + + let mut v = serde_json::Value::deserialize(deserializer)?; + let version = Version::deserialize(&v).map_err(de::Error::custom)?; + // remove "version", since v0 and v1 transactions use deny_unknown_fields + v.as_object_mut() + .expect("must be an object because deserializing version succeeded") + .remove("version"); + match version.version { + TransactionVersion::ZERO => Ok(Self::V0( + DeclareTransactionV0V1::deserialize(&v).map_err(de::Error::custom)?, + )), + TransactionVersion::ONE => Ok(Self::V1( + DeclareTransactionV0V1::deserialize(&v).map_err(de::Error::custom)?, + )), + TransactionVersion::TWO => Ok(Self::V2( + DeclareTransactionV2::deserialize(&v).map_err(de::Error::custom)?, + )), + TransactionVersion::THREE => Ok(Self::V3( + DeclareTransactionV3::deserialize(&v).map_err(de::Error::custom)?, + )), + _v => Err(de::Error::custom("version must be 0, 1, 2 or 3")), + } + } + } + + impl DeclareTransaction { + pub fn signature(&self) -> &[TransactionSignatureElem] { + match self { + DeclareTransaction::V0(tx) => tx.signature.as_ref(), + DeclareTransaction::V1(tx) => tx.signature.as_ref(), + DeclareTransaction::V2(tx) => tx.signature.as_ref(), + DeclareTransaction::V3(tx) => tx.signature.as_ref(), + } + } + } + + impl Dummy for DeclareTransaction { + fn dummy_with_rng(_: &T, rng: &mut R) -> Self { + match rng.gen_range(0..=3) { + 0 => { + let mut v0: DeclareTransactionV0V1 = Faker.fake_with_rng(rng); + v0.nonce = TransactionNonce::ZERO; + Self::V0(v0) + } + 1 => Self::V1(Faker.fake_with_rng(rng)), + 2 => Self::V2(Faker.fake_with_rng(rng)), + 3 => Self::V3(Faker.fake_with_rng(rng)), + _ => unreachable!(), + } + } + } + + /// A version 0 or 1 declare transaction. + #[serde_as] + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] + #[serde(deny_unknown_fields)] + pub struct DeclareTransactionV0V1 { + pub class_hash: ClassHash, + pub max_fee: Fee, + pub nonce: TransactionNonce, + pub sender_address: ContractAddress, + #[serde_as(as = "Vec")] + #[serde(default)] + pub signature: Vec, + pub transaction_hash: TransactionHash, + } + + /// A version 2 declare transaction. + #[serde_as] + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] + #[serde(deny_unknown_fields)] + pub struct DeclareTransactionV2 { + pub class_hash: ClassHash, + pub max_fee: Fee, + pub nonce: TransactionNonce, + pub sender_address: ContractAddress, + #[serde_as(as = "Vec")] + #[serde(default)] + pub signature: Vec, + pub transaction_hash: TransactionHash, + pub compiled_class_hash: CasmHash, + } + + /// A version 2 declare transaction. + #[serde_as] + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] + #[serde(deny_unknown_fields)] + pub struct DeclareTransactionV3 { + pub class_hash: ClassHash, + + pub nonce: TransactionNonce, + pub nonce_data_availability_mode: DataAvailabilityMode, + pub fee_data_availability_mode: DataAvailabilityMode, + pub resource_bounds: ResourceBounds, + #[serde_as(as = "TipAsHexStr")] + pub tip: Tip, + pub paymaster_data: Vec, + + pub sender_address: ContractAddress, + #[serde_as(as = "Vec")] + #[serde(default)] + pub signature: Vec, + pub transaction_hash: TransactionHash, + pub compiled_class_hash: CasmHash, + + pub account_deployment_data: Vec, + } + + const fn transaction_version_zero() -> TransactionVersion { + TransactionVersion::ZERO + } + + impl Dummy for DeclareTransactionV3 { + fn dummy_with_rng(_: &T, rng: &mut R) -> Self { + Self { + class_hash: Faker.fake_with_rng(rng), + + nonce: Faker.fake_with_rng(rng), + nonce_data_availability_mode: Faker.fake_with_rng(rng), + fee_data_availability_mode: Faker.fake_with_rng(rng), + resource_bounds: Faker.fake_with_rng(rng), + tip: Faker.fake_with_rng(rng), + paymaster_data: vec![Faker.fake_with_rng(rng)], // TODO p2p allows 1 elem only + + sender_address: Faker.fake_with_rng(rng), + signature: Faker.fake_with_rng(rng), + transaction_hash: Faker.fake_with_rng(rng), + compiled_class_hash: Faker.fake_with_rng(rng), + account_deployment_data: vec![Faker.fake_with_rng(rng)], // TODO p2p allows 1 elem only + } + } + } + + /// Represents deserialized L2 deploy transaction data. + #[serde_as] + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] + #[serde(deny_unknown_fields)] + pub struct DeployTransaction { + pub contract_address: ContractAddress, + pub contract_address_salt: ContractAddressSalt, + pub class_hash: ClassHash, + #[serde_as(as = "Vec")] + pub constructor_calldata: Vec, + pub transaction_hash: TransactionHash, + #[serde(default = "transaction_version_zero")] + pub version: TransactionVersion, + } + + impl Dummy for DeployTransaction { + fn dummy_with_rng(_: &T, rng: &mut R) -> Self { + Self { + version: TransactionVersion(Felt::from_u64(rng.gen_range(0..=1))), + contract_address: ContractAddress::ZERO, // Faker.fake_with_rng(rng), FIXME + contract_address_salt: Faker.fake_with_rng(rng), + class_hash: Faker.fake_with_rng(rng), + constructor_calldata: Faker.fake_with_rng(rng), + transaction_hash: Faker.fake_with_rng(rng), + } + } + } + + /// Represents deserialized L2 deploy account transaction data. + #[derive(Clone, Debug, Serialize, PartialEq, Eq, Dummy)] + #[serde(untagged)] + pub enum DeployAccountTransaction { + V0V1(DeployAccountTransactionV0V1), + V3(DeployAccountTransactionV3), + } + + impl<'de> Deserialize<'de> for DeployAccountTransaction { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use serde::de; + + #[serde_as] + #[derive(Deserialize)] + struct Version { + #[serde(default = "transaction_version_zero")] + pub version: TransactionVersion, + } + + let v = serde_json::Value::deserialize(deserializer)?; + let version = Version::deserialize(&v).map_err(de::Error::custom)?; + + match version.version { + TransactionVersion::ZERO => Ok(Self::V0V1( + DeployAccountTransactionV0V1::deserialize(&v).map_err(de::Error::custom)?, + )), + TransactionVersion::ONE => Ok(Self::V0V1( + DeployAccountTransactionV0V1::deserialize(&v).map_err(de::Error::custom)?, + )), + TransactionVersion::THREE => Ok(Self::V3( + DeployAccountTransactionV3::deserialize(&v).map_err(de::Error::custom)?, + )), + _v => Err(de::Error::custom("version must be 0, 1 or 3")), + } + } + } + + impl DeployAccountTransaction { + pub fn contract_address(&self) -> ContractAddress { + match self { + Self::V0V1(tx) => tx.contract_address, + Self::V3(tx) => tx.sender_address, + } + } + + pub fn signature(&self) -> &[TransactionSignatureElem] { + match self { + Self::V0V1(tx) => tx.signature.as_ref(), + Self::V3(tx) => tx.signature.as_ref(), + } + } + } + + #[serde_as] + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] + #[serde(deny_unknown_fields)] + pub struct DeployAccountTransactionV0V1 { + pub contract_address: ContractAddress, + pub transaction_hash: TransactionHash, + pub max_fee: Fee, + pub version: TransactionVersion, + #[serde_as(as = "Vec")] + pub signature: Vec, + pub nonce: TransactionNonce, + pub contract_address_salt: ContractAddressSalt, + #[serde_as(as = "Vec")] + pub constructor_calldata: Vec, + pub class_hash: ClassHash, + } + + impl Dummy for DeployAccountTransactionV0V1 { + fn dummy_with_rng(_: &T, rng: &mut R) -> Self { + let contract_address_salt = Faker.fake_with_rng(rng); + let constructor_calldata: Vec = Faker.fake_with_rng(rng); + let class_hash = Faker.fake_with_rng(rng); + + Self { + version: TransactionVersion::ONE, + contract_address: ContractAddress::deployed_contract_address( + constructor_calldata.iter().copied(), + &contract_address_salt, + &class_hash, + ), + transaction_hash: Faker.fake_with_rng(rng), + max_fee: Faker.fake_with_rng(rng), + signature: Faker.fake_with_rng(rng), + nonce: Faker.fake_with_rng(rng), + contract_address_salt, + constructor_calldata, + class_hash, + } + } + } + + #[serde_as] + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] + #[serde(deny_unknown_fields)] + pub struct DeployAccountTransactionV3 { + pub nonce: TransactionNonce, + pub nonce_data_availability_mode: DataAvailabilityMode, + pub fee_data_availability_mode: DataAvailabilityMode, + pub resource_bounds: ResourceBounds, + #[serde_as(as = "TipAsHexStr")] + pub tip: Tip, + pub paymaster_data: Vec, + + pub sender_address: ContractAddress, + #[serde_as(as = "Vec")] + pub signature: Vec, + pub transaction_hash: TransactionHash, + pub version: TransactionVersion, + pub contract_address_salt: ContractAddressSalt, + #[serde_as(as = "Vec")] + pub constructor_calldata: Vec, + pub class_hash: ClassHash, + } + + impl Dummy for DeployAccountTransactionV3 { + fn dummy_with_rng(_: &T, rng: &mut R) -> Self { + let contract_address_salt = Faker.fake_with_rng(rng); + let constructor_calldata: Vec = Faker.fake_with_rng(rng); + let class_hash = Faker.fake_with_rng(rng); + + Self { + nonce: Faker.fake_with_rng(rng), + nonce_data_availability_mode: Faker.fake_with_rng(rng), + fee_data_availability_mode: Faker.fake_with_rng(rng), + resource_bounds: Faker.fake_with_rng(rng), + tip: Faker.fake_with_rng(rng), + paymaster_data: vec![Faker.fake_with_rng(rng)], // TODO p2p allows 1 elem only + + sender_address: ContractAddress::deployed_contract_address( + constructor_calldata.iter().copied(), + &contract_address_salt, + &class_hash, + ), + signature: Faker.fake_with_rng(rng), + transaction_hash: Faker.fake_with_rng(rng), + version: TransactionVersion::THREE, + contract_address_salt, + constructor_calldata, + class_hash, + } + } + } + + #[derive(Clone, Debug, Serialize, PartialEq, Eq, Dummy)] + #[serde(tag = "version")] + pub enum InvokeTransaction { + #[serde(rename = "0x0")] + V0(InvokeTransactionV0), + #[serde(rename = "0x1")] + V1(InvokeTransactionV1), + #[serde(rename = "0x3")] + V3(InvokeTransactionV3), + } + + impl<'de> Deserialize<'de> for InvokeTransaction { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + use serde::de; + + #[serde_as] + #[derive(Deserialize)] + struct Version { + #[serde(default = "transaction_version_zero")] + pub version: TransactionVersion, + } + + let mut v = serde_json::Value::deserialize(deserializer)?; + let version = Version::deserialize(&v).map_err(de::Error::custom)?; + // remove "version", since v0 and v1 transactions use deny_unknown_fields + v.as_object_mut() + .expect("must be an object because deserializing version succeeded") + .remove("version"); + match version.version { + TransactionVersion::ZERO => Ok(Self::V0( + InvokeTransactionV0::deserialize(&v).map_err(de::Error::custom)?, + )), + TransactionVersion::ONE => Ok(Self::V1( + InvokeTransactionV1::deserialize(&v).map_err(de::Error::custom)?, + )), + TransactionVersion::THREE => Ok(Self::V3( + InvokeTransactionV3::deserialize(&v).map_err(de::Error::custom)?, + )), + _v => Err(de::Error::custom("version must be 0, 1 or 3")), + } + } + } + + impl InvokeTransaction { + pub fn signature(&self) -> &[TransactionSignatureElem] { + match self { + Self::V0(tx) => tx.signature.as_ref(), + Self::V1(tx) => tx.signature.as_ref(), + Self::V3(tx) => tx.signature.as_ref(), + } + } + } + + /// Represents deserialized L2 invoke transaction v0 data. + #[serde_as] + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] + #[serde(deny_unknown_fields)] + pub struct InvokeTransactionV0 { + #[serde_as(as = "Vec")] + pub calldata: Vec, + // contract_address is the historic name for this field. sender_address was + // introduced with starknet v0.11. Although the gateway no longer uses the historic + // name at all, this alias must be kept until a database migration fixes all historic + // transaction naming, or until regenesis removes them all. + #[serde(alias = "contract_address")] + pub sender_address: ContractAddress, + pub entry_point_selector: EntryPoint, + #[serde(skip_serializing_if = "Option::is_none")] + pub entry_point_type: Option, + pub max_fee: Fee, + #[serde_as(as = "Vec")] + pub signature: Vec, + pub transaction_hash: TransactionHash, + } + + impl Dummy for InvokeTransactionV0 { + fn dummy_with_rng(_: &T, rng: &mut R) -> Self { + Self { + calldata: Faker.fake_with_rng(rng), + sender_address: Faker.fake_with_rng(rng), + entry_point_selector: Faker.fake_with_rng(rng), + entry_point_type: None, + max_fee: Faker.fake_with_rng(rng), + signature: Faker.fake_with_rng(rng), + transaction_hash: Faker.fake_with_rng(rng), + } + } + } + + /// Represents deserialized L2 invoke transaction v1 data. + #[serde_as] + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] + #[serde(deny_unknown_fields)] + pub struct InvokeTransactionV1 { + #[serde_as(as = "Vec")] + pub calldata: Vec, + // contract_address is the historic name for this field. sender_address was + // introduced with starknet v0.11. Although the gateway no longer uses the historic + // name at all, this alias must be kept until a database migration fixes all historic + // transaction naming, or until regenesis removes them all. + #[serde(alias = "contract_address")] + pub sender_address: ContractAddress, + pub max_fee: Fee, + #[serde_as(as = "Vec")] + pub signature: Vec, + pub nonce: TransactionNonce, + pub transaction_hash: TransactionHash, + } + + /// Represents deserialized L2 invoke transaction v3 data. + #[serde_as] + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] + #[serde(deny_unknown_fields)] + pub struct InvokeTransactionV3 { + pub nonce: TransactionNonce, + pub nonce_data_availability_mode: DataAvailabilityMode, + pub fee_data_availability_mode: DataAvailabilityMode, + pub resource_bounds: ResourceBounds, + #[serde_as(as = "TipAsHexStr")] + pub tip: Tip, + pub paymaster_data: Vec, + + pub sender_address: ContractAddress, + #[serde_as(as = "Vec")] + pub signature: Vec, + pub transaction_hash: TransactionHash, + #[serde_as(as = "Vec")] + pub calldata: Vec, + + pub account_deployment_data: Vec, + } + + impl Dummy for InvokeTransactionV3 { + fn dummy_with_rng(_: &T, rng: &mut R) -> Self { + Self { + nonce: Faker.fake_with_rng(rng), + nonce_data_availability_mode: Faker.fake_with_rng(rng), + fee_data_availability_mode: Faker.fake_with_rng(rng), + resource_bounds: Faker.fake_with_rng(rng), + tip: Faker.fake_with_rng(rng), + paymaster_data: vec![Faker.fake_with_rng(rng)], // TODO p2p allows 1 elem only + + sender_address: Faker.fake_with_rng(rng), + signature: Faker.fake_with_rng(rng), + transaction_hash: Faker.fake_with_rng(rng), + calldata: Faker.fake_with_rng(rng), + account_deployment_data: vec![Faker.fake_with_rng(rng)], // TODO p2p allows 1 elem only + } + } + } + + /// Represents deserialized L2 "L1 handler" transaction data. + #[serde_as] + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] + #[serde(deny_unknown_fields)] + pub struct L1HandlerTransaction { + pub contract_address: ContractAddress, + pub entry_point_selector: EntryPoint, + // FIXME: remove once starkware fixes their gateway bug which was missing this field. + #[serde(default)] + pub nonce: TransactionNonce, + pub calldata: Vec, + pub transaction_hash: TransactionHash, + pub version: TransactionVersion, + } + + impl Dummy for L1HandlerTransaction { + fn dummy_with_rng(_: &T, rng: &mut R) -> Self { + Self { + // TODO verify this is the only realistic value + version: TransactionVersion::ZERO, + + contract_address: Faker.fake_with_rng(rng), + entry_point_selector: Faker.fake_with_rng(rng), + nonce: Faker.fake_with_rng(rng), + calldata: Faker.fake_with_rng(rng), + transaction_hash: Faker.fake_with_rng(rng), + } + } + } + + /// Describes L2 transaction failure details. + #[derive(Clone, Debug, Deserialize, PartialEq, Eq)] + #[serde(deny_unknown_fields)] + pub struct Failure { + pub code: String, + pub error_message: String, + } +} From 953158074e883bd7ba4b22a79b831ee674303c4a Mon Sep 17 00:00:00 2001 From: sistemd Date: Fri, 15 Mar 2024 01:30:15 +0100 Subject: [PATCH 02/24] fix migration --- crates/storage/src/schema.rs | 2 -- crates/storage/src/schema/revision_0051.rs | 29 ++++++++++++++++------ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/crates/storage/src/schema.rs b/crates/storage/src/schema.rs index 29462f740a..a438163edd 100644 --- a/crates/storage/src/schema.rs +++ b/crates/storage/src/schema.rs @@ -9,7 +9,6 @@ mod revision_0046; mod revision_0047; mod revision_0048; mod revision_0049; -mod revision_0050; mod revision_0051; pub(crate) use base::base_schema; @@ -28,7 +27,6 @@ pub fn migrations() -> &'static [MigrationFn] { revision_0047::migrate, revision_0048::migrate, revision_0049::migrate, - revision_0050::migrate, revision_0051::migrate, ] } diff --git a/crates/storage/src/schema/revision_0051.rs b/crates/storage/src/schema/revision_0051.rs index 3c1ce47430..e677542f5d 100644 --- a/crates/storage/src/schema/revision_0051.rs +++ b/crates/storage/src/schema/revision_0051.rs @@ -3,21 +3,28 @@ use std::mem; use anyhow::Context; use rusqlite::params; -/// NOTE: DO NOT RUN THIS IN PROD. -/// TODO: Update this to use bincode serialization instead of serde_json. (Follow-up PR) pub(crate) fn migrate(tx: &rusqlite::Transaction<'_>) -> anyhow::Result<()> { tx.execute( r" -ALTER TABLE starknet_transactions ADD COLUMN events BLOB DEFAULT NULL; -", + CREATE TABLE starknet_transactions_new ( + hash BLOB PRIMARY KEY, + idx INTEGER NOT NULL, + block_hash BLOB NOT NULL, + tx BLOB, + receipt BLOB, + events BLOB + )", [], ) - .context("Altering starknet_transactions table: adding new column")?; - let mut stmt = tx.prepare("SELECT hash, receipt, tx FROM starknet_transactions")?; + .context("Creating starknet_transactions_new table")?; + let mut stmt = + tx.prepare("SELECT hash, idx, block_hash, tx, receipt FROM starknet_transactions")?; let mut rows = stmt.query([])?; let mut compressor = zstd::bulk::Compressor::new(10).context("Create zstd compressor")?; while let Some(row) = rows.next()? { let hash = row.get_ref_unwrap("hash").as_blob()?; + let idx = row.get_ref_unwrap("idx").as_i64()?; + let block_hash = row.get_ref_unwrap("block_hash").as_blob()?; // Load old DTOs. let transaction = row.get_ref_unwrap("tx").as_blob()?; @@ -54,10 +61,16 @@ ALTER TABLE starknet_transactions ADD COLUMN events BLOB DEFAULT NULL; // Store the updated values. tx.execute( - "UPDATE starknet_transactions SET receipt = ?, events = ?, tx = ? WHERE hash = ?", - params![receipt, events, transaction, hash], + r"INSERT INTO starknet_transactions_new (hash, idx, block_hash, tx, receipt, events) + VALUES (?, ?, ?, ?, ?, ?)", + params![hash, idx, block_hash, transaction, receipt, events], )?; } + tx.execute("DROP TABLE starknet_transactions", [])?; + tx.execute( + "ALTER TABLE starknet_transactions_new RENAME TO starknet_transactions", + [], + )?; Ok(()) } From 7344093d60ea730f8338442470f123a4079ec127 Mon Sep 17 00:00:00 2001 From: sistemd Date: Fri, 15 Mar 2024 01:30:15 +0100 Subject: [PATCH 03/24] fix bugs and optimize --- crates/rpc/fixtures/mainnet.sqlite | Bin 393216 -> 471040 bytes .../src/v05/method/get_transaction_status.rs | 1 + crates/storage/src/connection.rs | 1 + crates/storage/src/connection/transaction.rs | 203 ++++-------------- crates/storage/src/fake.rs | 2 +- crates/storage/src/schema.rs | 2 + crates/storage/src/schema/revision_0051.rs | 58 ++--- 7 files changed, 81 insertions(+), 186 deletions(-) diff --git a/crates/rpc/fixtures/mainnet.sqlite b/crates/rpc/fixtures/mainnet.sqlite index a43c483542cdff7f82af230a7c49c146de966d2a..0f9449e8a59bb946ff4b05f33f16d1f0bb8d066f 100644 GIT binary patch delta 50915 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 G-v1w_Mp$3~ delta 450 zcmZp8Ak)wwF+o~Yj)8$80f=EB7|39Oaey=<5F1SvRFD^zW6+D2Vg(8D{{|_L;g2V7<=j&vKB(9B8-I_N)ZPv&`EC zdl}y|ZC6WY{Kv}DB*VhKT_czArjRR_D1!up8v|!6$4L%Bc4xL!)&(s8SkjqaGv_f~ zV3KDnVR+3i$~S>egtwjN2~P@x8~1T;4la;Kk-+qBEw-iG&*-t4Fm9J`V)S5QX%vxR z-=5vVxQ3O9!4B+-;4Vf%rtJpWY;%~W8L+WzXPLnG#~<0Rfl$A;-+Ipo#7scUJpI;t z7PV;tth%C(yfQrBc`os+<>}x_=CR|EPx# diff --git a/crates/rpc/src/v05/method/get_transaction_status.rs b/crates/rpc/src/v05/method/get_transaction_status.rs index 7f8f864e74..6a0245e609 100644 --- a/crates/rpc/src/v05/method/get_transaction_status.rs +++ b/crates/rpc/src/v05/method/get_transaction_status.rs @@ -199,6 +199,7 @@ mod tests { let context = RpcContext::for_tests(); // This transaction is in block 1 which is not L1 accepted. let tx_hash = transaction_hash_bytes!(b"txn 1"); + dbg!(&tx_hash); let input = GetTransactionStatusInput { transaction_hash: tx_hash, }; diff --git a/crates/storage/src/connection.rs b/crates/storage/src/connection.rs index 11afeddc34..89f6d8fb49 100644 --- a/crates/storage/src/connection.rs +++ b/crates/storage/src/connection.rs @@ -631,6 +631,7 @@ impl<'inner> Transaction<'inner> { } } +#[derive(Debug, Clone)] pub struct TransactionData { pub transaction: StarknetTransaction, pub receipt: Option, diff --git a/crates/storage/src/connection/transaction.rs b/crates/storage/src/connection/transaction.rs index e046715cd3..c761591654 100644 --- a/crates/storage/src/connection/transaction.rs +++ b/crates/storage/src/connection/transaction.rs @@ -24,6 +24,8 @@ pub(super) fn insert_transactions( return Ok(()); } + dbg!(&transaction_data); + let mut compressor = zstd::bulk::Compressor::new(10).context("Create zstd compressor")?; for ( i, @@ -487,13 +489,7 @@ pub(crate) mod dto { use fake::{Dummy, Fake, Faker}; use pathfinder_common::*; use pathfinder_crypto::Felt; - use pathfinder_serde::{ - CallParamAsDecimalStr, ConstructorParamAsDecimalStr, EthereumAddressAsHexStr, - L2ToL1MessagePayloadElemAsDecimalStr, ResourceAmountAsHexStr, ResourcePricePerUnitAsHexStr, - TipAsHexStr, TransactionSignatureElemAsDecimalStr, - }; use serde::{Deserialize, Serialize}; - use serde_with::serde_as; /// Represents deserialized L2 transaction entry point values. #[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] @@ -526,9 +522,7 @@ pub(crate) mod dto { #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] - #[serde(tag = "db_version")] pub enum Events { - #[serde(rename = "0")] V0 { events: Vec, }, @@ -685,14 +679,11 @@ pub(crate) mod dto { } /// Represents deserialized L2 to L1 message. - #[serde_as] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] #[serde(deny_unknown_fields)] pub struct L2ToL1Message { pub from_address: ContractAddress, - #[serde_as(as = "Vec")] pub payload: Vec, - #[serde_as(as = "EthereumAddressAsHexStr")] pub to_address: EthereumAddress, } @@ -738,9 +729,7 @@ pub(crate) mod dto { #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] - #[serde(tag = "db_version")] pub enum Receipt { - #[serde(rename = "0")] V0(ReceiptV0), } @@ -750,7 +739,6 @@ pub(crate) mod dto { pub struct ReceiptV0 { #[serde(default)] pub actual_fee: Option, - #[serde(skip_serializing_if = "Option::is_none")] pub execution_resources: Option, pub l2_to_l1_messages: Vec, pub transaction_hash: TransactionHash, @@ -835,38 +823,13 @@ pub(crate) mod dto { } } - #[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Dummy)] + #[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, PartialEq, Eq, Dummy)] pub enum DataAvailabilityMode { #[default] L1, L2, } - impl Serialize for DataAvailabilityMode { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - match self { - DataAvailabilityMode::L1 => serializer.serialize_u8(0), - DataAvailabilityMode::L2 => serializer.serialize_u8(1), - } - } - } - - impl<'de> Deserialize<'de> for DataAvailabilityMode { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - match ::deserialize(deserializer)? { - 0 => Ok(Self::L1), - 1 => Ok(Self::L2), - _ => Err(serde::de::Error::custom("invalid data availability mode")), - } - } - } - impl From for pathfinder_common::transaction::DataAvailabilityMode { fn from(value: DataAvailabilityMode) -> Self { match value { @@ -911,12 +874,9 @@ pub(crate) mod dto { } } - #[serde_as] #[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq, Dummy)] pub struct ResourceBound { - #[serde_as(as = "ResourceAmountAsHexStr")] pub max_amount: ResourceAmount, - #[serde_as(as = "ResourcePricePerUnitAsHexStr")] pub max_price_per_unit: ResourcePricePerUnit, } @@ -938,95 +898,26 @@ pub(crate) mod dto { } } - #[derive(Clone, Debug, Serialize, PartialEq, Eq, Dummy)] - #[serde(tag = "db_version")] + #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Dummy)] #[serde(deny_unknown_fields)] pub enum Transaction { - #[serde(rename = "0")] V0(TransactionV0), } /// Represents deserialized L2 transaction data. - #[derive(Clone, Debug, Serialize, PartialEq, Eq, Dummy)] - #[serde(tag = "type")] + #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Dummy)] #[serde(deny_unknown_fields)] pub enum TransactionV0 { - #[serde(rename = "DECLARE")] Declare(DeclareTransaction), - #[serde(rename = "DEPLOY")] // FIXME regenesis: remove Deploy txn type after regenesis // We are keeping this type of transaction until regenesis // only to support older pre-0.11.0 blocks Deploy(DeployTransaction), - #[serde(rename = "DEPLOY_ACCOUNT")] DeployAccount(DeployAccountTransaction), - #[serde(rename = "INVOKE_FUNCTION")] Invoke(InvokeTransaction), - #[serde(rename = "L1_HANDLER")] L1Handler(L1HandlerTransaction), } - // This manual deserializtion is a work-around for L1 handler transactions - // historically being served as Invoke V0. However, the gateway has retroactively - // changed these to L1 handlers. This means older databases will have these as Invoke - // but modern one's as L1 handler. This causes confusion, so we convert these old Invoke - // to L1 handler manually. - // - // The alternative is to do a costly database migration which involves opening every tx. - // - // This work-around may be removed once we are certain all databases no longer contain these - // transactions, which will likely only occur after either a migration, or regenesis. - impl<'de> Deserialize<'de> for Transaction { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - /// Copy of [Transaction] to deserialize into, before converting to [Transaction] - /// with the potential Invoke V0 -> L1 handler cast. - #[derive(Deserialize)] - #[serde(tag = "type", deny_unknown_fields)] - pub enum InnerTransaction { - #[serde(rename = "DECLARE")] - Declare(DeclareTransaction), - #[serde(rename = "DEPLOY")] - Deploy(DeployTransaction), - #[serde(rename = "DEPLOY_ACCOUNT")] - DeployAccount(DeployAccountTransaction), - #[serde(rename = "INVOKE_FUNCTION")] - Invoke(InvokeTransaction), - #[serde(rename = "L1_HANDLER")] - L1Handler(L1HandlerTransaction), - } - - let tx = InnerTransaction::deserialize(deserializer)?; - let tx = match tx { - InnerTransaction::Declare(x) => Transaction::V0(TransactionV0::Declare(x)), - InnerTransaction::Deploy(x) => Transaction::V0(TransactionV0::Deploy(x)), - InnerTransaction::DeployAccount(x) => { - Transaction::V0(TransactionV0::DeployAccount(x)) - } - InnerTransaction::Invoke(InvokeTransaction::V0(i)) - if i.entry_point_type == Some(EntryPointType::L1Handler) => - { - let l1_handler = L1HandlerTransaction { - contract_address: i.sender_address, - entry_point_selector: i.entry_point_selector, - nonce: TransactionNonce::ZERO, - calldata: i.calldata, - transaction_hash: i.transaction_hash, - version: TransactionVersion::ZERO, - }; - - Transaction::V0(TransactionV0::L1Handler(l1_handler)) - } - InnerTransaction::Invoke(x) => Transaction::V0(TransactionV0::Invoke(x)), - InnerTransaction::L1Handler(x) => Transaction::V0(TransactionV0::L1Handler(x)), - }; - - Ok(tx) - } - } - impl From<&pathfinder_common::transaction::Transaction> for Transaction { fn from(value: &pathfinder_common::transaction::Transaction) -> Self { use pathfinder_common::transaction::TransactionVariant::*; @@ -1642,15 +1533,10 @@ pub(crate) mod dto { } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] - #[serde(tag = "version")] pub enum DeclareTransaction { - #[serde(rename = "0x0")] V0(DeclareTransactionV0V1), - #[serde(rename = "0x1")] V1(DeclareTransactionV0V1), - #[serde(rename = "0x2")] V2(DeclareTransactionV2), - #[serde(rename = "0x3")] V3(DeclareTransactionV3), } @@ -1682,7 +1568,6 @@ pub(crate) mod dto { } /// A version 0 or 1 declare transaction. - #[serde_as] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] #[serde(deny_unknown_fields)] pub struct DeclareTransactionV0V1 { @@ -1690,14 +1575,12 @@ pub(crate) mod dto { pub max_fee: Fee, pub nonce: TransactionNonce, pub sender_address: ContractAddress, - #[serde_as(as = "Vec")] #[serde(default)] pub signature: Vec, pub transaction_hash: TransactionHash, } /// A version 2 declare transaction. - #[serde_as] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] #[serde(deny_unknown_fields)] pub struct DeclareTransactionV2 { @@ -1705,7 +1588,6 @@ pub(crate) mod dto { pub max_fee: Fee, pub nonce: TransactionNonce, pub sender_address: ContractAddress, - #[serde_as(as = "Vec")] #[serde(default)] pub signature: Vec, pub transaction_hash: TransactionHash, @@ -1713,7 +1595,6 @@ pub(crate) mod dto { } /// A version 2 declare transaction. - #[serde_as] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct DeclareTransactionV3 { @@ -1723,12 +1604,10 @@ pub(crate) mod dto { pub nonce_data_availability_mode: DataAvailabilityMode, pub fee_data_availability_mode: DataAvailabilityMode, pub resource_bounds: ResourceBounds, - #[serde_as(as = "TipAsHexStr")] pub tip: Tip, pub paymaster_data: Vec, pub sender_address: ContractAddress, - #[serde_as(as = "Vec")] #[serde(default)] pub signature: Vec, pub transaction_hash: TransactionHash, @@ -1763,14 +1642,12 @@ pub(crate) mod dto { } /// Represents deserialized L2 deploy transaction data. - #[serde_as] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct DeployTransaction { pub contract_address: ContractAddress, pub contract_address_salt: ContractAddressSalt, pub class_hash: ClassHash, - #[serde_as(as = "Vec")] pub constructor_calldata: Vec, pub transaction_hash: TransactionHash, #[serde(default = "transaction_version_zero")] @@ -1792,13 +1669,9 @@ pub(crate) mod dto { /// Represents deserialized L2 deploy account transaction data. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Dummy)] - #[serde(tag = "version")] pub enum DeployAccountTransaction { - #[serde(rename = "0x0")] V0(DeployAccountTransactionV0V1), - #[serde(rename = "0x1")] V1(DeployAccountTransactionV0V1), - #[serde(rename = "0x3")] V3(DeployAccountTransactionV3), } @@ -1818,18 +1691,15 @@ pub(crate) mod dto { } } - #[serde_as] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct DeployAccountTransactionV0V1 { pub contract_address: ContractAddress, pub transaction_hash: TransactionHash, pub max_fee: Fee, - #[serde_as(as = "Vec")] pub signature: Vec, pub nonce: TransactionNonce, pub contract_address_salt: ContractAddressSalt, - #[serde_as(as = "Vec")] pub constructor_calldata: Vec, pub class_hash: ClassHash, } @@ -1857,7 +1727,6 @@ pub(crate) mod dto { } } - #[serde_as] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct DeployAccountTransactionV3 { @@ -1865,16 +1734,13 @@ pub(crate) mod dto { pub nonce_data_availability_mode: DataAvailabilityMode, pub fee_data_availability_mode: DataAvailabilityMode, pub resource_bounds: ResourceBounds, - #[serde_as(as = "TipAsHexStr")] pub tip: Tip, pub paymaster_data: Vec, pub sender_address: ContractAddress, - #[serde_as(as = "Vec")] pub signature: Vec, pub transaction_hash: TransactionHash, pub contract_address_salt: ContractAddressSalt, - #[serde_as(as = "Vec")] pub constructor_calldata: Vec, pub class_hash: ClassHash, } @@ -1908,14 +1774,10 @@ pub(crate) mod dto { } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Dummy)] - #[serde(tag = "version")] #[serde(deny_unknown_fields)] pub enum InvokeTransaction { - #[serde(rename = "0x0")] V0(InvokeTransactionV0), - #[serde(rename = "0x1")] V1(InvokeTransactionV1), - #[serde(rename = "0x3")] V3(InvokeTransactionV3), } @@ -1930,23 +1792,18 @@ pub(crate) mod dto { } /// Represents deserialized L2 invoke transaction v0 data. - #[serde_as] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct InvokeTransactionV0 { - #[serde_as(as = "Vec")] pub calldata: Vec, // contract_address is the historic name for this field. sender_address was // introduced with starknet v0.11. Although the gateway no longer uses the historic // name at all, this alias must be kept until a database migration fixes all historic // transaction naming, or until regenesis removes them all. - #[serde(alias = "contract_address")] pub sender_address: ContractAddress, pub entry_point_selector: EntryPoint, - #[serde(skip_serializing_if = "Option::is_none")] pub entry_point_type: Option, pub max_fee: Fee, - #[serde_as(as = "Vec")] pub signature: Vec, pub transaction_hash: TransactionHash, } @@ -1966,11 +1823,9 @@ pub(crate) mod dto { } /// Represents deserialized L2 invoke transaction v1 data. - #[serde_as] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] #[serde(deny_unknown_fields)] pub struct InvokeTransactionV1 { - #[serde_as(as = "Vec")] pub calldata: Vec, // contract_address is the historic name for this field. sender_address was // introduced with starknet v0.11. Although the gateway no longer uses the historic @@ -1979,14 +1834,12 @@ pub(crate) mod dto { #[serde(alias = "contract_address")] pub sender_address: ContractAddress, pub max_fee: Fee, - #[serde_as(as = "Vec")] pub signature: Vec, pub nonce: TransactionNonce, pub transaction_hash: TransactionHash, } /// Represents deserialized L2 invoke transaction v3 data. - #[serde_as] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct InvokeTransactionV3 { @@ -1994,15 +1847,12 @@ pub(crate) mod dto { pub nonce_data_availability_mode: DataAvailabilityMode, pub fee_data_availability_mode: DataAvailabilityMode, pub resource_bounds: ResourceBounds, - #[serde_as(as = "TipAsHexStr")] pub tip: Tip, pub paymaster_data: Vec, pub sender_address: ContractAddress, - #[serde_as(as = "Vec")] pub signature: Vec, pub transaction_hash: TransactionHash, - #[serde_as(as = "Vec")] pub calldata: Vec, pub account_deployment_data: Vec, @@ -2028,7 +1878,6 @@ pub(crate) mod dto { } /// Represents deserialized L2 "L1 handler" transaction data. - #[serde_as] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct L1HandlerTransaction { @@ -2075,6 +1924,48 @@ mod tests { use super::*; + #[test] + fn transaction_dto() { + /* + use serde::{Deserialize, Serialize}; + use pathfinder_crypto::Felt; + + #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] + #[serde(deny_unknown_fields)] + pub enum Transaction { + V0 { value: Felt }, + V1 { value: Felt }, + } + + let original = Transaction::V0 { + value: Felt::from_u64(12), + }; + let serialized = + bincode::serde::encode_to_vec(&original, bincode::config::standard()).unwrap(); + let (outcome, _): (Transaction, _) = + bincode::serde::decode_from_slice(&serialized, bincode::config::standard()).unwrap(); + assert_eq!(original, outcome); + panic!(); + */ + + let original = pathfinder_common::transaction::Transaction { + hash: transaction_hash_bytes!(b"txn 1"), + variant: TransactionVariant::InvokeV0(InvokeTransactionV0 { + sender_address: contract_address_bytes!(b"contract 1"), + ..Default::default() + }), + }; + let serialized = bincode::serde::encode_to_vec( + dto::Transaction::from(&original), + bincode::config::standard(), + ) + .unwrap(); + let (deserialized, _): (dto::Transaction, _) = + bincode::serde::decode_from_slice(&serialized, bincode::config::standard()).unwrap(); + let outcome = pathfinder_common::transaction::Transaction::from(deserialized); + assert_eq!(original, outcome); + } + fn setup() -> ( crate::Connection, BlockHeader, diff --git a/crates/storage/src/fake.rs b/crates/storage/src/fake.rs index 22ad3d382a..b70096caa2 100644 --- a/crates/storage/src/fake.rs +++ b/crates/storage/src/fake.rs @@ -174,7 +174,7 @@ pub mod init { header.transaction_count = transaction_data.len(); header.event_count = transaction_data .iter() - .map(|(_, _, e)| e.events.len()) + .map(|(_, _, events)| events.len()) .sum(); let block_hash = header.hash; diff --git a/crates/storage/src/schema.rs b/crates/storage/src/schema.rs index a438163edd..29462f740a 100644 --- a/crates/storage/src/schema.rs +++ b/crates/storage/src/schema.rs @@ -9,6 +9,7 @@ mod revision_0046; mod revision_0047; mod revision_0048; mod revision_0049; +mod revision_0050; mod revision_0051; pub(crate) use base::base_schema; @@ -27,6 +28,7 @@ pub fn migrations() -> &'static [MigrationFn] { revision_0047::migrate, revision_0048::migrate, revision_0049::migrate, + revision_0050::migrate, revision_0051::migrate, ] } diff --git a/crates/storage/src/schema/revision_0051.rs b/crates/storage/src/schema/revision_0051.rs index e677542f5d..684a0aa504 100644 --- a/crates/storage/src/schema/revision_0051.rs +++ b/crates/storage/src/schema/revision_0051.rs @@ -130,7 +130,7 @@ pub(crate) mod old_dto { impl From<&ExecutionResources> for pathfinder_common::receipt::ExecutionResources { fn from(value: &ExecutionResources) -> Self { Self { - builtin_instance_counter: value.builtin_instance_counter.into(), + builtins: value.builtin_instance_counter.into(), n_steps: value.n_steps, n_memory_holes: value.n_memory_holes, data_availability: match (value.l1_gas, value.l1_data_gas) { @@ -149,7 +149,7 @@ pub(crate) mod old_dto { impl From<&pathfinder_common::receipt::ExecutionResources> for ExecutionResources { fn from(value: &pathfinder_common::receipt::ExecutionResources) -> Self { Self { - builtin_instance_counter: (&value.builtin_instance_counter).into(), + builtin_instance_counter: (&value.builtins).into(), n_steps: value.n_steps, n_memory_holes: value.n_memory_holes, l1_gas: Some(value.data_availability.l1_gas), @@ -208,15 +208,15 @@ pub(crate) mod old_dto { segment_arena_builtin, } = value; Self { - output_builtin, - pedersen_builtin, - range_check_builtin, - ecdsa_builtin, - bitwise_builtin, - ec_op_builtin, - keccak_builtin, - poseidon_builtin, - segment_arena_builtin, + output: output_builtin, + pedersen: pedersen_builtin, + range_check: range_check_builtin, + ecdsa: ecdsa_builtin, + bitwise: bitwise_builtin, + ec_op: ec_op_builtin, + keccak: keccak_builtin, + poseidon: poseidon_builtin, + segment_arena: segment_arena_builtin, } } } @@ -225,26 +225,26 @@ pub(crate) mod old_dto { fn from(value: &pathfinder_common::receipt::BuiltinCounters) -> Self { // Use deconstruction to ensure these structs remain in-sync. let pathfinder_common::receipt::BuiltinCounters { - output_builtin, - pedersen_builtin, - range_check_builtin, - ecdsa_builtin, - bitwise_builtin, - ec_op_builtin, - keccak_builtin, - poseidon_builtin, - segment_arena_builtin, + output, + pedersen, + range_check, + ecdsa, + bitwise, + ec_op, + keccak, + poseidon, + segment_arena, } = value.clone(); Self { - output_builtin, - pedersen_builtin, - range_check_builtin, - ecdsa_builtin, - bitwise_builtin, - ec_op_builtin, - keccak_builtin, - poseidon_builtin, - segment_arena_builtin, + output_builtin: output, + pedersen_builtin: pedersen, + range_check_builtin: range_check, + ecdsa_builtin: ecdsa, + bitwise_builtin: bitwise, + ec_op_builtin: ec_op, + keccak_builtin: keccak, + poseidon_builtin: poseidon, + segment_arena_builtin: segment_arena, } } } From fc9f0943669f5b702c447dbb6adce2e2e9c083e9 Mon Sep 17 00:00:00 2001 From: sistemd Date: Fri, 15 Mar 2024 01:30:15 +0100 Subject: [PATCH 04/24] get rid of unnecessary serde(rename) --- .../src/v05/method/get_transaction_status.rs | 1 - .../src/v07/method/estimate_message_fee.rs | 2 - crates/storage/src/connection/transaction.rs | 49 ------------------- 3 files changed, 52 deletions(-) diff --git a/crates/rpc/src/v05/method/get_transaction_status.rs b/crates/rpc/src/v05/method/get_transaction_status.rs index 6a0245e609..7f8f864e74 100644 --- a/crates/rpc/src/v05/method/get_transaction_status.rs +++ b/crates/rpc/src/v05/method/get_transaction_status.rs @@ -199,7 +199,6 @@ mod tests { let context = RpcContext::for_tests(); // This transaction is in block 1 which is not L1 accepted. let tx_hash = transaction_hash_bytes!(b"txn 1"); - dbg!(&tx_hash); let input = GetTransactionStatusInput { transaction_hash: tx_hash, }; diff --git a/crates/rpc/src/v07/method/estimate_message_fee.rs b/crates/rpc/src/v07/method/estimate_message_fee.rs index d6988ecfc1..65a3b0c45f 100644 --- a/crates/rpc/src/v07/method/estimate_message_fee.rs +++ b/crates/rpc/src/v07/method/estimate_message_fee.rs @@ -130,8 +130,6 @@ mod tests { #[tokio::test] async fn test_estimate_message_fee() { - dbg!(EntryPoint::hashed(b"l1_handler")); - let expected = FeeEstimate { gas_consumed: 14647.into(), gas_price: 2.into(), diff --git a/crates/storage/src/connection/transaction.rs b/crates/storage/src/connection/transaction.rs index c761591654..7323ca154f 100644 --- a/crates/storage/src/connection/transaction.rs +++ b/crates/storage/src/connection/transaction.rs @@ -24,8 +24,6 @@ pub(super) fn insert_transactions( return Ok(()); } - dbg!(&transaction_data); - let mut compressor = zstd::bulk::Compressor::new(10).context("Create zstd compressor")?; for ( i, @@ -495,9 +493,7 @@ pub(crate) mod dto { #[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] #[serde(deny_unknown_fields)] pub enum EntryPointType { - #[serde(rename = "EXTERNAL")] External, - #[serde(rename = "L1_HANDLER")] L1Handler, } @@ -718,7 +714,6 @@ pub(crate) mod dto { } #[derive(Clone, Default, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] - #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum ExecutionStatus { // This must be the default as pre v0.12.1 receipts did not contain this value and // were always success as reverted did not exist. @@ -850,9 +845,7 @@ pub(crate) mod dto { #[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq, Dummy)] pub struct ResourceBounds { - #[serde(rename = "L1_GAS")] pub l1_gas: ResourceBound, - #[serde(rename = "L2_GAS")] pub l2_gas: ResourceBound, } @@ -1924,48 +1917,6 @@ mod tests { use super::*; - #[test] - fn transaction_dto() { - /* - use serde::{Deserialize, Serialize}; - use pathfinder_crypto::Felt; - - #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] - #[serde(deny_unknown_fields)] - pub enum Transaction { - V0 { value: Felt }, - V1 { value: Felt }, - } - - let original = Transaction::V0 { - value: Felt::from_u64(12), - }; - let serialized = - bincode::serde::encode_to_vec(&original, bincode::config::standard()).unwrap(); - let (outcome, _): (Transaction, _) = - bincode::serde::decode_from_slice(&serialized, bincode::config::standard()).unwrap(); - assert_eq!(original, outcome); - panic!(); - */ - - let original = pathfinder_common::transaction::Transaction { - hash: transaction_hash_bytes!(b"txn 1"), - variant: TransactionVariant::InvokeV0(InvokeTransactionV0 { - sender_address: contract_address_bytes!(b"contract 1"), - ..Default::default() - }), - }; - let serialized = bincode::serde::encode_to_vec( - dto::Transaction::from(&original), - bincode::config::standard(), - ) - .unwrap(); - let (deserialized, _): (dto::Transaction, _) = - bincode::serde::decode_from_slice(&serialized, bincode::config::standard()).unwrap(); - let outcome = pathfinder_common::transaction::Transaction::from(deserialized); - assert_eq!(original, outcome); - } - fn setup() -> ( crate::Connection, BlockHeader, From 8bc67769c184788df9570c6dc3dc58450fba31d8 Mon Sep 17 00:00:00 2001 From: sistemd Date: Fri, 15 Mar 2024 01:30:15 +0100 Subject: [PATCH 05/24] add log --- crates/storage/src/schema/revision_0051.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/storage/src/schema/revision_0051.rs b/crates/storage/src/schema/revision_0051.rs index 684a0aa504..1b133b3dea 100644 --- a/crates/storage/src/schema/revision_0051.rs +++ b/crates/storage/src/schema/revision_0051.rs @@ -4,6 +4,8 @@ use anyhow::Context; use rusqlite::params; pub(crate) fn migrate(tx: &rusqlite::Transaction<'_>) -> anyhow::Result<()> { + tracing::info!("migrating starknet_transactions to new format"); + tx.execute( r" CREATE TABLE starknet_transactions_new ( From 4e995ba392c0f33ed4ff3c2c3dab63f4566eda61 Mon Sep 17 00:00:00 2001 From: sistemd Date: Mon, 18 Mar 2024 11:49:20 +0100 Subject: [PATCH 06/24] use prepared statement for insertion --- crates/storage/src/schema/revision_0051.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/storage/src/schema/revision_0051.rs b/crates/storage/src/schema/revision_0051.rs index 1b133b3dea..827fcee880 100644 --- a/crates/storage/src/schema/revision_0051.rs +++ b/crates/storage/src/schema/revision_0051.rs @@ -19,9 +19,13 @@ pub(crate) fn migrate(tx: &rusqlite::Transaction<'_>) -> anyhow::Result<()> { [], ) .context("Creating starknet_transactions_new table")?; - let mut stmt = + let mut query = tx.prepare("SELECT hash, idx, block_hash, tx, receipt FROM starknet_transactions")?; - let mut rows = stmt.query([])?; + let mut insert = tx.prepare( + r"INSERT INTO starknet_transactions_new (hash, idx, block_hash, tx, receipt, events) + VALUES (?, ?, ?, ?, ?, ?)", + )?; + let mut rows = query.query([])?; let mut compressor = zstd::bulk::Compressor::new(10).context("Create zstd compressor")?; while let Some(row) = rows.next()? { let hash = row.get_ref_unwrap("hash").as_blob()?; @@ -62,11 +66,7 @@ pub(crate) fn migrate(tx: &rusqlite::Transaction<'_>) -> anyhow::Result<()> { let events = compressor.compress(&events).context("Compressing events")?; // Store the updated values. - tx.execute( - r"INSERT INTO starknet_transactions_new (hash, idx, block_hash, tx, receipt, events) - VALUES (?, ?, ?, ?, ?, ?)", - params![hash, idx, block_hash, transaction, receipt, events], - )?; + insert.execute(params![hash, idx, block_hash, transaction, receipt, events])?; } tx.execute("DROP TABLE starknet_transactions", [])?; tx.execute( From 120f35df3f30aab3115764e42b9e25c8005016e4 Mon Sep 17 00:00:00 2001 From: sistemd Date: Mon, 18 Mar 2024 13:54:44 +0100 Subject: [PATCH 07/24] parallelize migration --- Cargo.lock | 25 ++++ crates/rpc/fixtures/mainnet.sqlite | Bin 471040 -> 471040 bytes crates/storage/Cargo.toml | 1 + crates/storage/src/schema/revision_0051.rs | 159 +++++++++++++++------ 4 files changed, 140 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 77a709dbb0..e742d64f82 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3594,6 +3594,18 @@ dependencies = [ "num-traits 0.2.17", ] +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "spin 0.9.8", +] + [[package]] name = "fnv" version = "1.0.7" @@ -5581,6 +5593,15 @@ dependencies = [ "unsigned-varint 0.7.2", ] +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom", +] + [[package]] name = "native-tls" version = "0.2.11" @@ -6369,6 +6390,7 @@ dependencies = [ "const_format", "data-encoding", "fake", + "flume", "hex", "lazy_static", "pathfinder-common", @@ -7890,6 +7912,9 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] [[package]] name = "spki" diff --git a/crates/rpc/fixtures/mainnet.sqlite b/crates/rpc/fixtures/mainnet.sqlite index 0f9449e8a59bb946ff4b05f33f16d1f0bb8d066f..025332025d575bcbf9089c6ccd1f7ea3b6e137b5 100644 GIT binary patch delta 40 vcmZp8Ak*+bW`Z=M@) -> anyhow::Result<()> { tracing::info!("migrating starknet_transactions to new format"); + let mut transformers = Vec::new(); + let (insert_tx, insert_rx) = mpsc::channel(); + let (transform_tx, transform_rx) = + flume::unbounded::<(Vec, i64, Vec, Vec, Vec)>(); + for _ in 0..thread::available_parallelism().unwrap().get() { + let insert_tx = insert_tx.clone(); + let transform_rx = transform_rx.clone(); + let mut compressor = zstd::bulk::Compressor::new(10).context("Create zstd compressor")?; + let transformer = thread::spawn(move || { + for (hash, idx, block_hash, transaction, receipt) in transform_rx.iter() { + // Load old DTOs. + let transaction = zstd::decode_all(transaction.as_slice()) + .context("Decompressing transaction") + .unwrap(); + let transaction: old_dto::Transaction = serde_json::from_slice(&transaction) + .context("Deserializing transaction") + .unwrap(); + let transaction = pathfinder_common::transaction::Transaction::from(transaction); + let receipt = zstd::decode_all(receipt.as_slice()) + .context("Decompressing receipt") + .unwrap(); + let mut receipt: old_dto::Receipt = serde_json::from_slice(&receipt) + .context("Deserializing receipt") + .unwrap(); + let events = mem::take(&mut receipt.events); + let receipt = pathfinder_common::receipt::Receipt::from(receipt); + + // Serialize into new DTOs. + let transaction = crate::transaction::dto::Transaction::from(&transaction); + let transaction = + bincode::serde::encode_to_vec(transaction, bincode::config::standard()) + .context("Serializing transaction") + .unwrap(); + let transaction = compressor + .compress(&transaction) + .context("Compressing transaction") + .unwrap(); + let receipt = crate::transaction::dto::Receipt::from(&receipt); + let receipt = bincode::serde::encode_to_vec(receipt, bincode::config::standard()) + .context("Serializing receipt") + .unwrap(); + let receipt = compressor + .compress(&receipt) + .context("Compressing receipt") + .unwrap(); + let events = bincode::serde::encode_to_vec( + crate::transaction::dto::Events::V0 { events }, + bincode::config::standard(), + ) + .context("Serializing events") + .unwrap(); + let events = compressor + .compress(&events) + .context("Compressing events") + .unwrap(); + + // Store the updated values. + if let Err(err) = + insert_tx.send((hash, idx, block_hash, transaction, receipt, events)) + { + panic!("Failed to send transaction: {:?}", err); + } + } + }); + transformers.push(transformer); + } + tx.execute( r" CREATE TABLE starknet_transactions_new ( @@ -19,55 +86,57 @@ pub(crate) fn migrate(tx: &rusqlite::Transaction<'_>) -> anyhow::Result<()> { [], ) .context("Creating starknet_transactions_new table")?; - let mut query = + let mut query_stmt = tx.prepare("SELECT hash, idx, block_hash, tx, receipt FROM starknet_transactions")?; - let mut insert = tx.prepare( + let mut insert_stmt = tx.prepare( r"INSERT INTO starknet_transactions_new (hash, idx, block_hash, tx, receipt, events) VALUES (?, ?, ?, ?, ?, ?)", )?; - let mut rows = query.query([])?; - let mut compressor = zstd::bulk::Compressor::new(10).context("Create zstd compressor")?; - while let Some(row) = rows.next()? { - let hash = row.get_ref_unwrap("hash").as_blob()?; - let idx = row.get_ref_unwrap("idx").as_i64()?; - let block_hash = row.get_ref_unwrap("block_hash").as_blob()?; - - // Load old DTOs. - let transaction = row.get_ref_unwrap("tx").as_blob()?; - let transaction = zstd::decode_all(transaction).context("Decompressing transaction")?; - let transaction: old_dto::Transaction = - serde_json::from_slice(&transaction).context("Deserializing transaction")?; - let transaction = pathfinder_common::transaction::Transaction::from(transaction); - let receipt = row.get_ref_unwrap("receipt").as_blob()?; - let receipt = zstd::decode_all(receipt).context("Decompressing receipt")?; - let mut receipt: old_dto::Receipt = - serde_json::from_slice(&receipt).context("Deserializing receipt")?; - let events = mem::take(&mut receipt.events); - let receipt = pathfinder_common::receipt::Receipt::from(receipt); - - // Serialize into new DTOs. - let transaction = crate::transaction::dto::Transaction::from(&transaction); - let transaction = bincode::serde::encode_to_vec(transaction, bincode::config::standard()) - .context("Serializing transaction")?; - let transaction = compressor - .compress(&transaction) - .context("Compressing transaction")?; - let receipt = crate::transaction::dto::Receipt::from(&receipt); - let receipt = bincode::serde::encode_to_vec(receipt, bincode::config::standard()) - .context("Serializing receipt")?; - let receipt = compressor - .compress(&receipt) - .context("Compressing receipt")?; - let events = bincode::serde::encode_to_vec( - crate::transaction::dto::Events::V0 { events }, - bincode::config::standard(), - ) - .context("Serializing events")?; - let events = compressor.compress(&events).context("Compressing events")?; - - // Store the updated values. - insert.execute(params![hash, idx, block_hash, transaction, receipt, events])?; + const BATCH_SIZE: usize = 10_000; + let mut rows = query_stmt.query([])?; + loop { + let mut batch_size = 0; + for _ in 0..BATCH_SIZE { + match rows.next() { + Ok(Some(row)) => { + let hash = row.get_ref_unwrap("hash").as_blob()?; + let idx = row.get_ref_unwrap("idx").as_i64()?; + let block_hash = row.get_ref_unwrap("block_hash").as_blob()?; + let transaction = row.get_ref_unwrap("tx").as_blob()?; + let receipt = row.get_ref_unwrap("receipt").as_blob()?; + transform_tx + .send(( + hash.to_vec(), + idx, + block_hash.to_vec(), + transaction.to_vec(), + receipt.to_vec(), + )) + .context("Sending transaction to transformer")?; + batch_size += 1; + } + Ok(None) => break, + Err(err) => return Err(err.into()), + } + } + for _ in 0..batch_size { + let (hash, idx, block_hash, transaction, receipt, events) = insert_rx.recv()?; + insert_stmt.execute(params![hash, idx, block_hash, transaction, receipt, events])?; + } + if batch_size < BATCH_SIZE { + // This was the last batch. + break; + } } + + drop(insert_tx); + drop(transform_tx); + + // Ensure that all transformers have finished successfully. + for transformer in transformers { + transformer.join().unwrap(); + } + tx.execute("DROP TABLE starknet_transactions", [])?; tx.execute( "ALTER TABLE starknet_transactions_new RENAME TO starknet_transactions", From 9a0ba1bec9e181a22ed93ebc53f6205af80912eb Mon Sep 17 00:00:00 2001 From: sistemd Date: Mon, 18 Mar 2024 15:50:33 +0100 Subject: [PATCH 08/24] minimal felts --- Cargo.lock | 3 + crates/common/src/lib.rs | 28 +- crates/rpc/fixtures/mainnet.sqlite | Bin 471040 -> 462848 bytes crates/storage/Cargo.toml | 2 +- crates/storage/src/connection/transaction.rs | 895 +++++++++++-------- crates/storage/src/fake.rs | 2 +- crates/storage/src/schema/revision_0051.rs | 4 +- 7 files changed, 544 insertions(+), 390 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e742d64f82..9e0e179cf7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7828,6 +7828,9 @@ name = "smallvec" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +dependencies = [ + "serde", +] [[package]] name = "smol" diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index 405b777d50..07624df5f0 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -472,40 +472,40 @@ impl From for StarknetVersion { macros::felt_newtypes!( [ - AccountDeploymentDataElem, BlockHash, ByteCodeOffset, BlockCommitmentSignatureElem, - CallParam, CallResultValue, ClassCommitment, ClassCommitmentLeafHash, - ClassHash, - ConstructorParam, - ContractAddressSalt, ContractNonce, ContractStateHash, ContractRoot, - EntryPoint, EventCommitment, - EventData, - EventKey, - Fee, L1ToL2MessageNonce, L1ToL2MessagePayloadElem, - L2ToL1MessagePayloadElem, - PaymasterDataElem, SequencerAddress, StateCommitment, StateDiffCommitment, StorageCommitment, StorageValue, TransactionCommitment, - TransactionHash, - TransactionNonce, - TransactionSignatureElem, ]; [ + EntryPoint, + CallParam, + ConstructorParam, + ContractAddressSalt, + AccountDeploymentDataElem, + PaymasterDataElem, + TransactionSignatureElem, + TransactionNonce, + ClassHash, + EventKey, + EventData, + TransactionHash, + Fee, + L2ToL1MessagePayloadElem, ContractAddress, SierraHash, CasmHash, diff --git a/crates/rpc/fixtures/mainnet.sqlite b/crates/rpc/fixtures/mainnet.sqlite index 025332025d575bcbf9089c6ccd1f7ea3b6e137b5..23b1085f985591faa6755a1f41033de2eceee926 100644 GIT binary patch delta 25209 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�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

}`@O2y(LIvXCF6Q~<&<%o?R7+nWK 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%0Al-zq@wRU0t8Ly|0gWDc`>O?XJ+JKA+z^re;h} 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 F`#%)|*Y*Ga diff --git a/crates/storage/Cargo.toml b/crates/storage/Cargo.toml index 6c7021dbf5..fb2fa9b727 100644 --- a/crates/storage/Cargo.toml +++ b/crates/storage/Cargo.toml @@ -36,7 +36,7 @@ serde_json = { workspace = true, features = [ ] } serde_with = { workspace = true } sha3 = { workspace = true } -smallvec = { workspace = true } +smallvec = { workspace = true, features = ["serde"] } starknet-gateway-types = { path = "../gateway-types" } thiserror = { workspace = true } tokio = { workspace = true } diff --git a/crates/storage/src/connection/transaction.rs b/crates/storage/src/connection/transaction.rs index 7323ca154f..776883652c 100644 --- a/crates/storage/src/connection/transaction.rs +++ b/crates/storage/src/connection/transaction.rs @@ -62,7 +62,7 @@ pub(super) fn insert_transactions( Some(events) => { let events = bincode::serde::encode_to_vec( &dto::Events::V0 { - events: events.clone(), + events: events.iter().map(|x| x.to_owned().into()).collect(), }, bincode::config::standard(), ) @@ -219,7 +219,7 @@ pub(super) fn transaction_with_receipt( transaction.into(), receipt.into(), match events { - dto::Events::V0 { events } => events, + dto::Events::V0 { events } => events.into_iter().map(Into::into).collect(), }, block_hash, ))) @@ -355,7 +355,7 @@ pub(super) fn transaction_data_for_block( transaction.into(), receipt.into(), match events { - dto::Events::V0 { events } => events, + dto::Events::V0 { events } => events.into_iter().map(Into::into).collect(), }, )); } @@ -440,7 +440,7 @@ pub(super) fn events_for_block( data.push(( hash, match events { - dto::Events::V0 { events } => events, + dto::Events::V0 { events } => events.into_iter().map(Into::into).collect(), }, )); } @@ -488,6 +488,41 @@ pub(crate) mod dto { use pathfinder_common::*; use pathfinder_crypto::Felt; use serde::{Deserialize, Serialize}; + use smallvec::SmallVec; + + /// Minimally encoded Felt value. + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Default)] + #[serde(deny_unknown_fields)] + pub struct MinimalFelt(SmallVec<[u8; 32]>); + + impl Dummy for MinimalFelt { + fn dummy_with_rng(config: &T, rng: &mut R) -> Self { + let felt: Felt = Dummy::dummy_with_rng(config, rng); + felt.into() + } + } + + impl From for MinimalFelt { + fn from(value: Felt) -> Self { + Self( + value + .to_be_bytes() + .into_iter() + .skip_while(|&x| x == 0) + .collect(), + ) + } + } + + impl From for Felt { + fn from(value: MinimalFelt) -> Self { + let mut bytes = [0; 32]; + let num_zeros = bytes.len() - value.0.len(); + bytes[..num_zeros].fill(0); + bytes[num_zeros..].copy_from_slice(&value.0); + Felt::from_be_bytes(bytes).unwrap() + } + } /// Represents deserialized L2 transaction entry point values. #[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] @@ -519,9 +554,54 @@ pub(crate) mod dto { #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub enum Events { - V0 { - events: Vec, - }, + V0 { events: Vec }, + } + + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] + #[serde(deny_unknown_fields)] + pub struct Event { + pub data: Vec, + pub from_address: MinimalFelt, + pub keys: Vec, + } + + impl From for Event { + fn from(value: pathfinder_common::event::Event) -> Self { + let pathfinder_common::event::Event { + data, + from_address, + keys, + } = value; + Self { + data: data + .into_iter() + .map(|x| x.as_inner().to_owned().into()) + .collect(), + from_address: from_address.as_inner().to_owned().into(), + keys: keys + .into_iter() + .map(|x| x.as_inner().to_owned().into()) + .collect(), + } + } + } + + impl From for pathfinder_common::event::Event { + fn from(value: Event) -> Self { + Self { + data: value + .data + .into_iter() + .map(|x| EventData::new_or_panic(x.into())) + .collect(), + from_address: ContractAddress::new_or_panic(value.from_address.into()), + keys: value + .keys + .into_iter() + .map(|x| EventKey::new_or_panic(x.into())) + .collect(), + } + } } /// Represents execution resources for L2 transaction. @@ -678,8 +758,8 @@ pub(crate) mod dto { #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] #[serde(deny_unknown_fields)] pub struct L2ToL1Message { - pub from_address: ContractAddress, - pub payload: Vec, + pub from_address: MinimalFelt, + pub payload: Vec, pub to_address: EthereumAddress, } @@ -691,8 +771,11 @@ pub(crate) mod dto { to_address, } = value; pathfinder_common::receipt::L2ToL1Message { - from_address, - payload, + from_address: ContractAddress::new_or_panic(from_address.into()), + payload: payload + .into_iter() + .map(|x| L2ToL1MessagePayloadElem::new_or_panic(x.into())) + .collect(), to_address, } } @@ -706,8 +789,11 @@ pub(crate) mod dto { to_address, } = value.clone(); Self { - from_address, - payload, + from_address: from_address.as_inner().to_owned().into(), + payload: payload + .into_iter() + .map(|x| x.as_inner().to_owned().into()) + .collect(), to_address, } } @@ -733,10 +819,10 @@ pub(crate) mod dto { #[serde(deny_unknown_fields)] pub struct ReceiptV0 { #[serde(default)] - pub actual_fee: Option, + pub actual_fee: Option, pub execution_resources: Option, pub l2_to_l1_messages: Vec, - pub transaction_hash: TransactionHash, + pub transaction_hash: MinimalFelt, pub transaction_index: TransactionIndex, // Introduced in v0.12.1 #[serde(default)] @@ -763,10 +849,10 @@ pub(crate) mod dto { }) = value; common::Receipt { - actual_fee, + actual_fee: actual_fee.map(|x| Fee::new_or_panic(x.into())), execution_resources: (&execution_resources.unwrap_or_default()).into(), l2_to_l1_messages: l2_to_l1_messages.into_iter().map(Into::into).collect(), - transaction_hash, + transaction_hash: TransactionHash::new_or_panic(transaction_hash.into()), transaction_index, execution_status: match execution_status { ExecutionStatus::Succeeded => common::ExecutionStatus::Succeeded, @@ -788,10 +874,10 @@ pub(crate) mod dto { }; Self::V0(ReceiptV0 { - actual_fee: value.actual_fee, + actual_fee: value.actual_fee.map(|x| x.as_inner().to_owned().into()), execution_resources: Some((&value.execution_resources).into()), l2_to_l1_messages: value.l2_to_l1_messages.iter().map(Into::into).collect(), - transaction_hash: value.transaction_hash, + transaction_hash: value.transaction_hash.as_inner().to_owned().into(), transaction_index: value.transaction_index, execution_status, revert_error, @@ -926,12 +1012,15 @@ pub(crate) mod dto { signature, }) => Self::V0(TransactionV0::Declare(DeclareTransaction::V0( self::DeclareTransactionV0V1 { - class_hash, - max_fee, - nonce, - sender_address, - signature, - transaction_hash, + class_hash: class_hash.as_inner().to_owned().into(), + max_fee: max_fee.as_inner().to_owned().into(), + nonce: nonce.as_inner().to_owned().into(), + sender_address: sender_address.as_inner().to_owned().into(), + signature: signature + .into_iter() + .map(|x| x.as_inner().to_owned().into()) + .collect(), + transaction_hash: transaction_hash.as_inner().to_owned().into(), }, ))), DeclareV1(DeclareTransactionV0V1 { @@ -942,12 +1031,15 @@ pub(crate) mod dto { signature, }) => Self::V0(TransactionV0::Declare(DeclareTransaction::V1( self::DeclareTransactionV0V1 { - class_hash, - max_fee, - nonce, - sender_address, - signature, - transaction_hash, + class_hash: class_hash.as_inner().to_owned().into(), + max_fee: max_fee.as_inner().to_owned().into(), + nonce: nonce.as_inner().to_owned().into(), + sender_address: sender_address.as_inner().to_owned().into(), + signature: signature + .into_iter() + .map(|x| x.as_inner().to_owned().into()) + .collect(), + transaction_hash: transaction_hash.as_inner().to_owned().into(), }, ))), DeclareV2(DeclareTransactionV2 { @@ -959,13 +1051,16 @@ pub(crate) mod dto { compiled_class_hash, }) => Self::V0(TransactionV0::Declare(DeclareTransaction::V2( self::DeclareTransactionV2 { - class_hash, - max_fee, - nonce, - sender_address, - signature, - transaction_hash, - compiled_class_hash, + class_hash: class_hash.as_inner().to_owned().into(), + max_fee: max_fee.as_inner().to_owned().into(), + nonce: nonce.as_inner().to_owned().into(), + sender_address: sender_address.as_inner().to_owned().into(), + signature: signature + .into_iter() + .map(|x| x.as_inner().to_owned().into()) + .collect(), + transaction_hash: transaction_hash.as_inner().to_owned().into(), + compiled_class_hash: compiled_class_hash.as_inner().to_owned().into(), }, ))), DeclareV3(DeclareTransactionV3 { @@ -982,18 +1077,27 @@ pub(crate) mod dto { compiled_class_hash, }) => Self::V0(TransactionV0::Declare(DeclareTransaction::V3( self::DeclareTransactionV3 { - class_hash, - nonce, + class_hash: class_hash.as_inner().to_owned().into(), + nonce: nonce.as_inner().to_owned().into(), nonce_data_availability_mode: nonce_data_availability_mode.into(), fee_data_availability_mode: fee_data_availability_mode.into(), resource_bounds: resource_bounds.into(), tip, - paymaster_data, - sender_address, - signature, - transaction_hash, - compiled_class_hash, - account_deployment_data, + paymaster_data: paymaster_data + .into_iter() + .map(|x| x.as_inner().to_owned().into()) + .collect(), + sender_address: sender_address.as_inner().to_owned().into(), + signature: signature + .into_iter() + .map(|x| x.as_inner().to_owned().into()) + .collect(), + transaction_hash: transaction_hash.as_inner().to_owned().into(), + compiled_class_hash: compiled_class_hash.as_inner().to_owned().into(), + account_deployment_data: account_deployment_data + .into_iter() + .map(|x| x.as_inner().to_owned().into()) + .collect(), }, ))), Deploy(DeployTransaction { @@ -1003,12 +1107,15 @@ pub(crate) mod dto { constructor_calldata, version, }) => Self::V0(TransactionV0::Deploy(self::DeployTransaction { - contract_address, - contract_address_salt, - class_hash, - constructor_calldata, - transaction_hash, - version, + contract_address: contract_address.as_inner().to_owned().into(), + contract_address_salt: contract_address_salt.as_inner().to_owned().into(), + class_hash: class_hash.as_inner().to_owned().into(), + constructor_calldata: constructor_calldata + .into_iter() + .map(|x| x.as_inner().to_owned().into()) + .collect(), + transaction_hash: transaction_hash.as_inner().to_owned().into(), + version: version.0.into(), })), DeployAccountV0V1(DeployAccountTransactionV0V1 { contract_address, @@ -1022,14 +1129,23 @@ pub(crate) mod dto { }) if version == TransactionVersion::ZERO => { Self::V0(TransactionV0::DeployAccount( self::DeployAccountTransaction::V0(self::DeployAccountTransactionV0V1 { - contract_address, - transaction_hash, - max_fee, - signature, - nonce, - contract_address_salt, - constructor_calldata, - class_hash, + contract_address: contract_address.as_inner().to_owned().into(), + transaction_hash: transaction_hash.as_inner().to_owned().into(), + max_fee: max_fee.as_inner().to_owned().into(), + signature: signature + .into_iter() + .map(|x| x.as_inner().to_owned().into()) + .collect(), + nonce: nonce.as_inner().to_owned().into(), + contract_address_salt: contract_address_salt + .as_inner() + .to_owned() + .into(), + constructor_calldata: constructor_calldata + .into_iter() + .map(|x| x.as_inner().to_owned().into()) + .collect(), + class_hash: class_hash.as_inner().to_owned().into(), }), )) } @@ -1044,14 +1160,20 @@ pub(crate) mod dto { class_hash, }) if version == TransactionVersion::ONE => Self::V0(TransactionV0::DeployAccount( self::DeployAccountTransaction::V1(self::DeployAccountTransactionV0V1 { - contract_address, - transaction_hash, - max_fee, - signature, - nonce, - contract_address_salt, - constructor_calldata, - class_hash, + contract_address: contract_address.as_inner().to_owned().into(), + transaction_hash: transaction_hash.as_inner().to_owned().into(), + max_fee: max_fee.as_inner().to_owned().into(), + signature: signature + .into_iter() + .map(|x| x.as_inner().to_owned().into()) + .collect(), + nonce: nonce.as_inner().to_owned().into(), + contract_address_salt: contract_address_salt.as_inner().to_owned().into(), + constructor_calldata: constructor_calldata + .into_iter() + .map(|x| x.as_inner().to_owned().into()) + .collect(), + class_hash: class_hash.as_inner().to_owned().into(), }), )), DeployAccountV0V1(DeployAccountTransactionV0V1 { version, .. }) => { @@ -1071,18 +1193,27 @@ pub(crate) mod dto { class_hash, }) => Self::V0(TransactionV0::DeployAccount( self::DeployAccountTransaction::V3(self::DeployAccountTransactionV3 { - nonce, + nonce: nonce.as_inner().to_owned().into(), nonce_data_availability_mode: nonce_data_availability_mode.into(), fee_data_availability_mode: fee_data_availability_mode.into(), resource_bounds: resource_bounds.into(), tip, - paymaster_data, - sender_address: contract_address, - signature, - transaction_hash, - contract_address_salt, - constructor_calldata, - class_hash, + paymaster_data: paymaster_data + .into_iter() + .map(|x| x.as_inner().to_owned().into()) + .collect(), + sender_address: contract_address.as_inner().to_owned().into(), + signature: signature + .into_iter() + .map(|x| x.as_inner().to_owned().into()) + .collect(), + transaction_hash: transaction_hash.as_inner().to_owned().into(), + contract_address_salt: contract_address_salt.as_inner().to_owned().into(), + constructor_calldata: constructor_calldata + .into_iter() + .map(|x| x.as_inner().to_owned().into()) + .collect(), + class_hash: class_hash.as_inner().to_owned().into(), }), )), InvokeV0(InvokeTransactionV0 { @@ -1094,13 +1225,19 @@ pub(crate) mod dto { signature, }) => Self::V0(TransactionV0::Invoke(InvokeTransaction::V0( self::InvokeTransactionV0 { - calldata, - sender_address, - entry_point_selector, + calldata: calldata + .into_iter() + .map(|x| x.as_inner().to_owned().into()) + .collect(), + sender_address: sender_address.as_inner().to_owned().into(), + entry_point_selector: entry_point_selector.as_inner().to_owned().into(), entry_point_type: entry_point_type.map(Into::into), - max_fee, - signature, - transaction_hash, + max_fee: max_fee.as_inner().to_owned().into(), + signature: signature + .into_iter() + .map(|x| x.as_inner().to_owned().into()) + .collect(), + transaction_hash: transaction_hash.as_inner().to_owned().into(), }, ))), InvokeV1(InvokeTransactionV1 { @@ -1111,12 +1248,18 @@ pub(crate) mod dto { nonce, }) => Self::V0(TransactionV0::Invoke(InvokeTransaction::V1( self::InvokeTransactionV1 { - calldata, - sender_address, - max_fee, - signature, - nonce, - transaction_hash, + calldata: calldata + .into_iter() + .map(|x| x.as_inner().to_owned().into()) + .collect(), + sender_address: sender_address.as_inner().to_owned().into(), + max_fee: max_fee.as_inner().to_owned().into(), + signature: signature + .into_iter() + .map(|x| x.as_inner().to_owned().into()) + .collect(), + nonce: nonce.as_inner().to_owned().into(), + transaction_hash: transaction_hash.as_inner().to_owned().into(), }, ))), InvokeV3(InvokeTransactionV3 { @@ -1132,17 +1275,29 @@ pub(crate) mod dto { sender_address, }) => Self::V0(TransactionV0::Invoke(InvokeTransaction::V3( self::InvokeTransactionV3 { - nonce, + nonce: nonce.as_inner().to_owned().into(), nonce_data_availability_mode: nonce_data_availability_mode.into(), fee_data_availability_mode: fee_data_availability_mode.into(), resource_bounds: resource_bounds.into(), tip, - paymaster_data, - sender_address, - signature, - transaction_hash, - calldata, - account_deployment_data, + paymaster_data: paymaster_data + .into_iter() + .map(|x| x.as_inner().to_owned().into()) + .collect(), + sender_address: sender_address.as_inner().to_owned().into(), + signature: signature + .into_iter() + .map(|x| x.as_inner().to_owned().into()) + .collect(), + transaction_hash: transaction_hash.as_inner().to_owned().into(), + calldata: calldata + .into_iter() + .map(|x| x.as_inner().to_owned().into()) + .collect(), + account_deployment_data: account_deployment_data + .into_iter() + .map(|x| x.as_inner().to_owned().into()) + .collect(), }, ))), L1Handler(L1HandlerTransaction { @@ -1151,12 +1306,15 @@ pub(crate) mod dto { nonce, calldata, }) => Self::V0(TransactionV0::L1Handler(self::L1HandlerTransaction { - contract_address, - entry_point_selector, - nonce, - calldata, - transaction_hash, - version: TransactionVersion::ZERO, + contract_address: contract_address.as_inner().to_owned().into(), + entry_point_selector: entry_point_selector.as_inner().to_owned().into(), + nonce: nonce.as_inner().to_owned().into(), + calldata: calldata + .into_iter() + .map(|x| x.as_inner().to_owned().into()) + .collect(), + transaction_hash: transaction_hash.as_inner().to_owned().into(), + version: TransactionVersion::ZERO.0.into(), })), } } @@ -1179,11 +1337,14 @@ pub(crate) mod dto { }, ))) => TransactionVariant::DeclareV0( pathfinder_common::transaction::DeclareTransactionV0V1 { - class_hash, - max_fee, - nonce, - sender_address, - signature, + class_hash: ClassHash::new_or_panic(class_hash.into()), + max_fee: Fee::new_or_panic(max_fee.into()), + nonce: TransactionNonce::new_or_panic(nonce.into()), + sender_address: ContractAddress::new_or_panic(sender_address.into()), + signature: signature + .into_iter() + .map(|x| TransactionSignatureElem::new_or_panic(x.into())) + .collect(), }, ), Transaction::V0(TransactionV0::Declare(DeclareTransaction::V1( @@ -1197,11 +1358,14 @@ pub(crate) mod dto { }, ))) => TransactionVariant::DeclareV1( pathfinder_common::transaction::DeclareTransactionV0V1 { - class_hash, - max_fee, - nonce, - sender_address, - signature, + class_hash: ClassHash::new_or_panic(class_hash.into()), + max_fee: Fee::new_or_panic(max_fee.into()), + nonce: TransactionNonce::new_or_panic(nonce.into()), + sender_address: ContractAddress::new_or_panic(sender_address.into()), + signature: signature + .into_iter() + .map(|x| TransactionSignatureElem::new_or_panic(x.into())) + .collect(), }, ), Transaction::V0(TransactionV0::Declare(DeclareTransaction::V2( @@ -1216,12 +1380,15 @@ pub(crate) mod dto { }, ))) => TransactionVariant::DeclareV2( pathfinder_common::transaction::DeclareTransactionV2 { - class_hash, - max_fee, - nonce, - sender_address, - signature, - compiled_class_hash, + class_hash: ClassHash::new_or_panic(class_hash.into()), + max_fee: Fee::new_or_panic(max_fee.into()), + nonce: TransactionNonce::new_or_panic(nonce.into()), + sender_address: ContractAddress::new_or_panic(sender_address.into()), + signature: signature + .into_iter() + .map(|x| TransactionSignatureElem::new_or_panic(x.into())) + .collect(), + compiled_class_hash: CasmHash::new_or_panic(compiled_class_hash.into()), }, ), Transaction::V0(TransactionV0::Declare(DeclareTransaction::V3( @@ -1241,17 +1408,26 @@ pub(crate) mod dto { }, ))) => TransactionVariant::DeclareV3( pathfinder_common::transaction::DeclareTransactionV3 { - class_hash, - nonce, + class_hash: ClassHash::new_or_panic(class_hash.into()), + nonce: TransactionNonce::new_or_panic(nonce.into()), nonce_data_availability_mode: nonce_data_availability_mode.into(), fee_data_availability_mode: fee_data_availability_mode.into(), resource_bounds: resource_bounds.into(), tip, - paymaster_data, - sender_address, - signature, - compiled_class_hash, - account_deployment_data, + paymaster_data: paymaster_data + .into_iter() + .map(|x| PaymasterDataElem::new_or_panic(x.into())) + .collect(), + sender_address: ContractAddress::new_or_panic(sender_address.into()), + signature: signature + .into_iter() + .map(|x| TransactionSignatureElem::new_or_panic(x.into())) + .collect(), + compiled_class_hash: CasmHash::new_or_panic(compiled_class_hash.into()), + account_deployment_data: account_deployment_data + .into_iter() + .map(|x| AccountDeploymentDataElem::new_or_panic(x.into())) + .collect(), }, ), Transaction::V0(TransactionV0::Deploy(DeployTransaction { @@ -1263,11 +1439,16 @@ pub(crate) mod dto { version, })) => { TransactionVariant::Deploy(pathfinder_common::transaction::DeployTransaction { - contract_address, - contract_address_salt, - class_hash, - constructor_calldata, - version, + contract_address: ContractAddress::new_or_panic(contract_address.into()), + contract_address_salt: ContractAddressSalt::new_or_panic( + contract_address_salt.into(), + ), + class_hash: ClassHash::new_or_panic(class_hash.into()), + constructor_calldata: constructor_calldata + .into_iter() + .map(|x| ConstructorParam::new_or_panic(x.into())) + .collect(), + version: TransactionVersion(version.into()), }) } Transaction::V0(TransactionV0::DeployAccount(DeployAccountTransaction::V0( @@ -1283,14 +1464,22 @@ pub(crate) mod dto { }, ))) => TransactionVariant::DeployAccountV0V1( pathfinder_common::transaction::DeployAccountTransactionV0V1 { - contract_address, - max_fee, + contract_address: ContractAddress::new_or_panic(contract_address.into()), + max_fee: Fee::new_or_panic(max_fee.into()), version: TransactionVersion::ZERO, - signature, - nonce, - contract_address_salt, - constructor_calldata, - class_hash, + signature: signature + .into_iter() + .map(|x| TransactionSignatureElem::new_or_panic(x.into())) + .collect(), + nonce: TransactionNonce::new_or_panic(nonce.into()), + contract_address_salt: ContractAddressSalt::new_or_panic( + contract_address_salt.into(), + ), + constructor_calldata: constructor_calldata + .into_iter() + .map(|x| CallParam::new_or_panic(x.into())) + .collect(), + class_hash: ClassHash::new_or_panic(class_hash.into()), }, ), Transaction::V0(TransactionV0::DeployAccount(DeployAccountTransaction::V1( @@ -1306,14 +1495,22 @@ pub(crate) mod dto { }, ))) => TransactionVariant::DeployAccountV0V1( pathfinder_common::transaction::DeployAccountTransactionV0V1 { - contract_address, - max_fee, + contract_address: ContractAddress::new_or_panic(contract_address.into()), + max_fee: Fee::new_or_panic(max_fee.into()), version: TransactionVersion::ONE, - signature, - nonce, - contract_address_salt, - constructor_calldata, - class_hash, + signature: signature + .into_iter() + .map(|x| TransactionSignatureElem::new_or_panic(x.into())) + .collect(), + nonce: TransactionNonce::new_or_panic(nonce.into()), + contract_address_salt: ContractAddressSalt::new_or_panic( + contract_address_salt.into(), + ), + constructor_calldata: constructor_calldata + .into_iter() + .map(|x| CallParam::new_or_panic(x.into())) + .collect(), + class_hash: ClassHash::new_or_panic(class_hash.into()), }, ), Transaction::V0(TransactionV0::DeployAccount(DeployAccountTransaction::V3( @@ -1333,17 +1530,28 @@ pub(crate) mod dto { }, ))) => TransactionVariant::DeployAccountV3( pathfinder_common::transaction::DeployAccountTransactionV3 { - contract_address: sender_address, - signature, - nonce, + contract_address: ContractAddress::new_or_panic(sender_address.into()), + signature: signature + .into_iter() + .map(|x| TransactionSignatureElem::new_or_panic(x.into())) + .collect(), + nonce: TransactionNonce::new_or_panic(nonce.into()), nonce_data_availability_mode: nonce_data_availability_mode.into(), fee_data_availability_mode: fee_data_availability_mode.into(), resource_bounds: resource_bounds.into(), tip, - paymaster_data, - contract_address_salt, - constructor_calldata, - class_hash, + paymaster_data: paymaster_data + .into_iter() + .map(|x| PaymasterDataElem::new_or_panic(x.into())) + .collect(), + contract_address_salt: ContractAddressSalt::new_or_panic( + contract_address_salt.into(), + ), + constructor_calldata: constructor_calldata + .into_iter() + .map(|x| CallParam::new_or_panic(x.into())) + .collect(), + class_hash: ClassHash::new_or_panic(class_hash.into()), }, ), Transaction::V0(TransactionV0::Invoke(InvokeTransaction::V0( @@ -1358,12 +1566,18 @@ pub(crate) mod dto { }, ))) => TransactionVariant::InvokeV0( pathfinder_common::transaction::InvokeTransactionV0 { - calldata, - sender_address, - entry_point_selector, + calldata: calldata + .into_iter() + .map(|x| CallParam::new_or_panic(x.into())) + .collect(), + sender_address: ContractAddress::new_or_panic(sender_address.into()), + entry_point_selector: EntryPoint::new_or_panic(entry_point_selector.into()), entry_point_type: entry_point_type.map(Into::into), - max_fee, - signature, + max_fee: Fee::new_or_panic(max_fee.into()), + signature: signature + .into_iter() + .map(|x| TransactionSignatureElem::new_or_panic(x.into())) + .collect(), }, ), Transaction::V0(TransactionV0::Invoke(InvokeTransaction::V1( @@ -1377,11 +1591,17 @@ pub(crate) mod dto { }, ))) => TransactionVariant::InvokeV1( pathfinder_common::transaction::InvokeTransactionV1 { - calldata, - sender_address, - max_fee, - signature, - nonce, + calldata: calldata + .into_iter() + .map(|x| CallParam::new_or_panic(x.into())) + .collect(), + sender_address: ContractAddress::new_or_panic(sender_address.into()), + max_fee: Fee::new_or_panic(max_fee.into()), + signature: signature + .into_iter() + .map(|x| TransactionSignatureElem::new_or_panic(x.into())) + .collect(), + nonce: TransactionNonce::new_or_panic(nonce.into()), }, ), Transaction::V0(TransactionV0::Invoke(InvokeTransaction::V3( @@ -1400,16 +1620,28 @@ pub(crate) mod dto { }, ))) => TransactionVariant::InvokeV3( pathfinder_common::transaction::InvokeTransactionV3 { - signature, - nonce, + signature: signature + .into_iter() + .map(|x| TransactionSignatureElem::new_or_panic(x.into())) + .collect(), + nonce: TransactionNonce::new_or_panic(nonce.into()), nonce_data_availability_mode: nonce_data_availability_mode.into(), fee_data_availability_mode: fee_data_availability_mode.into(), resource_bounds: resource_bounds.into(), tip, - paymaster_data, - account_deployment_data, - calldata, - sender_address, + paymaster_data: paymaster_data + .into_iter() + .map(|x| PaymasterDataElem::new_or_panic(x.into())) + .collect(), + account_deployment_data: account_deployment_data + .into_iter() + .map(|x| AccountDeploymentDataElem::new_or_panic(x.into())) + .collect(), + calldata: calldata + .into_iter() + .map(|x| CallParam::new_or_panic(x.into())) + .collect(), + sender_address: ContractAddress::new_or_panic(sender_address.into()), }, ), Transaction::V0(TransactionV0::L1Handler(L1HandlerTransaction { @@ -1422,10 +1654,13 @@ pub(crate) mod dto { version: _, })) => TransactionVariant::L1Handler( pathfinder_common::transaction::L1HandlerTransaction { - contract_address, - entry_point_selector, - nonce, - calldata, + contract_address: ContractAddress::new_or_panic(contract_address.into()), + entry_point_selector: EntryPoint::new_or_panic(entry_point_selector.into()), + nonce: TransactionNonce::new_or_panic(nonce.into()), + calldata: calldata + .into_iter() + .map(|x| CallParam::new_or_panic(x.into())) + .collect(), }, ), }; @@ -1437,91 +1672,30 @@ pub(crate) mod dto { impl Transaction { /// Returns hash of the transaction pub fn hash(&self) -> TransactionHash { - match self { - Transaction::V0(TransactionV0::Declare(t)) => match t { - DeclareTransaction::V0(t) => t.transaction_hash, - DeclareTransaction::V1(t) => t.transaction_hash, - DeclareTransaction::V2(t) => t.transaction_hash, - DeclareTransaction::V3(t) => t.transaction_hash, - }, - Transaction::V0(TransactionV0::Deploy(t)) => t.transaction_hash, - Transaction::V0(TransactionV0::DeployAccount(t)) => match t { - DeployAccountTransaction::V0(t) | DeployAccountTransaction::V1(t) => { - t.transaction_hash - } - DeployAccountTransaction::V3(t) => t.transaction_hash, - }, - Transaction::V0(TransactionV0::Invoke(t)) => match t { - InvokeTransaction::V0(t) => t.transaction_hash, - InvokeTransaction::V1(t) => t.transaction_hash, - InvokeTransaction::V3(t) => t.transaction_hash, - }, - Transaction::V0(TransactionV0::L1Handler(t)) => t.transaction_hash, - } - } - - pub fn contract_address(&self) -> ContractAddress { - match self { - Transaction::V0(TransactionV0::Declare(DeclareTransaction::V0(t))) => { - t.sender_address - } - Transaction::V0(TransactionV0::Declare(DeclareTransaction::V1(t))) => { - t.sender_address - } - Transaction::V0(TransactionV0::Declare(DeclareTransaction::V2(t))) => { - t.sender_address - } - Transaction::V0(TransactionV0::Declare(DeclareTransaction::V3(t))) => { - t.sender_address - } - Transaction::V0(TransactionV0::Deploy(t)) => t.contract_address, - Transaction::V0(TransactionV0::DeployAccount(t)) => match t { - DeployAccountTransaction::V0(t) | DeployAccountTransaction::V1(t) => { - t.contract_address - } - DeployAccountTransaction::V3(t) => t.sender_address, - }, - Transaction::V0(TransactionV0::Invoke(t)) => match t { - InvokeTransaction::V0(t) => t.sender_address, - InvokeTransaction::V1(t) => t.sender_address, - InvokeTransaction::V3(t) => t.sender_address, - }, - Transaction::V0(TransactionV0::L1Handler(t)) => t.contract_address, - } - } - - pub fn version(&self) -> TransactionVersion { - match self { - Transaction::V0(TransactionV0::Declare(DeclareTransaction::V0(_))) => { - TransactionVersion::ZERO - } - Transaction::V0(TransactionV0::Declare(DeclareTransaction::V1(_))) => { - TransactionVersion::ONE - } - Transaction::V0(TransactionV0::Declare(DeclareTransaction::V2(_))) => { - TransactionVersion::TWO - } - Transaction::V0(TransactionV0::Declare(DeclareTransaction::V3(_))) => { - TransactionVersion::THREE - } - - Transaction::V0(TransactionV0::Deploy(t)) => t.version, - Transaction::V0(TransactionV0::DeployAccount(t)) => match t { - DeployAccountTransaction::V0(..) => TransactionVersion::ZERO, - DeployAccountTransaction::V1(..) => TransactionVersion::ONE, - DeployAccountTransaction::V3(..) => TransactionVersion::THREE, - }, - Transaction::V0(TransactionV0::Invoke(InvokeTransaction::V0(_))) => { - TransactionVersion::ZERO - } - Transaction::V0(TransactionV0::Invoke(InvokeTransaction::V1(_))) => { - TransactionVersion::ONE - } - Transaction::V0(TransactionV0::Invoke(InvokeTransaction::V3(_))) => { - TransactionVersion::THREE + TransactionHash::new_or_panic( + match self { + Transaction::V0(TransactionV0::Declare(t)) => match t { + DeclareTransaction::V0(t) => t.transaction_hash.clone(), + DeclareTransaction::V1(t) => t.transaction_hash.clone(), + DeclareTransaction::V2(t) => t.transaction_hash.clone(), + DeclareTransaction::V3(t) => t.transaction_hash.clone(), + }, + Transaction::V0(TransactionV0::Deploy(t)) => t.transaction_hash.clone(), + Transaction::V0(TransactionV0::DeployAccount(t)) => match t { + DeployAccountTransaction::V0(t) | DeployAccountTransaction::V1(t) => { + t.transaction_hash.clone() + } + DeployAccountTransaction::V3(t) => t.transaction_hash.clone(), + }, + Transaction::V0(TransactionV0::Invoke(t)) => match t { + InvokeTransaction::V0(t) => t.transaction_hash.clone(), + InvokeTransaction::V1(t) => t.transaction_hash.clone(), + InvokeTransaction::V3(t) => t.transaction_hash.clone(), + }, + Transaction::V0(TransactionV0::L1Handler(t)) => t.transaction_hash.clone(), } - Transaction::V0(TransactionV0::L1Handler(t)) => t.version, - } + .into(), + ) } } @@ -1533,23 +1707,12 @@ pub(crate) mod dto { V3(DeclareTransactionV3), } - impl DeclareTransaction { - pub fn signature(&self) -> &[TransactionSignatureElem] { - match self { - DeclareTransaction::V0(tx) => tx.signature.as_ref(), - DeclareTransaction::V1(tx) => tx.signature.as_ref(), - DeclareTransaction::V2(tx) => tx.signature.as_ref(), - DeclareTransaction::V3(tx) => tx.signature.as_ref(), - } - } - } - impl Dummy for DeclareTransaction { fn dummy_with_rng(_: &T, rng: &mut R) -> Self { match rng.gen_range(0..=3) { 0 => { let mut v0: DeclareTransactionV0V1 = Faker.fake_with_rng(rng); - v0.nonce = TransactionNonce::ZERO; + v0.nonce = TransactionNonce::ZERO.0.into(); Self::V0(v0) } 1 => Self::V1(Faker.fake_with_rng(rng)), @@ -1564,53 +1727,53 @@ pub(crate) mod dto { #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] #[serde(deny_unknown_fields)] pub struct DeclareTransactionV0V1 { - pub class_hash: ClassHash, - pub max_fee: Fee, - pub nonce: TransactionNonce, - pub sender_address: ContractAddress, + pub class_hash: MinimalFelt, + pub max_fee: MinimalFelt, + pub nonce: MinimalFelt, + pub sender_address: MinimalFelt, #[serde(default)] - pub signature: Vec, - pub transaction_hash: TransactionHash, + pub signature: Vec, + pub transaction_hash: MinimalFelt, } /// A version 2 declare transaction. #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] #[serde(deny_unknown_fields)] pub struct DeclareTransactionV2 { - pub class_hash: ClassHash, - pub max_fee: Fee, - pub nonce: TransactionNonce, - pub sender_address: ContractAddress, + pub class_hash: MinimalFelt, + pub max_fee: MinimalFelt, + pub nonce: MinimalFelt, + pub sender_address: MinimalFelt, #[serde(default)] - pub signature: Vec, - pub transaction_hash: TransactionHash, - pub compiled_class_hash: CasmHash, + pub signature: Vec, + pub transaction_hash: MinimalFelt, + pub compiled_class_hash: MinimalFelt, } /// A version 2 declare transaction. #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct DeclareTransactionV3 { - pub class_hash: ClassHash, + pub class_hash: MinimalFelt, - pub nonce: TransactionNonce, + pub nonce: MinimalFelt, pub nonce_data_availability_mode: DataAvailabilityMode, pub fee_data_availability_mode: DataAvailabilityMode, pub resource_bounds: ResourceBounds, pub tip: Tip, - pub paymaster_data: Vec, + pub paymaster_data: Vec, - pub sender_address: ContractAddress, + pub sender_address: MinimalFelt, #[serde(default)] - pub signature: Vec, - pub transaction_hash: TransactionHash, - pub compiled_class_hash: CasmHash, + pub signature: Vec, + pub transaction_hash: MinimalFelt, + pub compiled_class_hash: MinimalFelt, - pub account_deployment_data: Vec, + pub account_deployment_data: Vec, } - const fn transaction_version_zero() -> TransactionVersion { - TransactionVersion::ZERO + fn transaction_version_zero() -> MinimalFelt { + TransactionVersion::ZERO.0.into() } impl Dummy for DeclareTransactionV3 { @@ -1638,20 +1801,20 @@ pub(crate) mod dto { #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct DeployTransaction { - pub contract_address: ContractAddress, - pub contract_address_salt: ContractAddressSalt, - pub class_hash: ClassHash, - pub constructor_calldata: Vec, - pub transaction_hash: TransactionHash, + pub contract_address: MinimalFelt, + pub contract_address_salt: MinimalFelt, + pub class_hash: MinimalFelt, + pub constructor_calldata: Vec, + pub transaction_hash: MinimalFelt, #[serde(default = "transaction_version_zero")] - pub version: TransactionVersion, + pub version: MinimalFelt, } impl Dummy for DeployTransaction { fn dummy_with_rng(_: &T, rng: &mut R) -> Self { Self { - version: TransactionVersion(Felt::from_u64(rng.gen_range(0..=1))), - contract_address: ContractAddress::ZERO, // Faker.fake_with_rng(rng), FIXME + version: Felt::from_u64(rng.gen_range(0..=1)).into(), + contract_address: ContractAddress::ZERO.0.into(), // Faker.fake_with_rng(rng), FIXME contract_address_salt: Faker.fake_with_rng(rng), class_hash: Faker.fake_with_rng(rng), constructor_calldata: Faker.fake_with_rng(rng), @@ -1668,33 +1831,17 @@ pub(crate) mod dto { V3(DeployAccountTransactionV3), } - impl DeployAccountTransaction { - pub fn contract_address(&self) -> ContractAddress { - match self { - Self::V0(tx) | Self::V1(tx) => tx.contract_address, - Self::V3(tx) => tx.sender_address, - } - } - - pub fn signature(&self) -> &[TransactionSignatureElem] { - match self { - Self::V0(tx) | Self::V1(tx) => tx.signature.as_ref(), - Self::V3(tx) => tx.signature.as_ref(), - } - } - } - #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct DeployAccountTransactionV0V1 { - pub contract_address: ContractAddress, - pub transaction_hash: TransactionHash, - pub max_fee: Fee, - pub signature: Vec, - pub nonce: TransactionNonce, - pub contract_address_salt: ContractAddressSalt, - pub constructor_calldata: Vec, - pub class_hash: ClassHash, + pub contract_address: MinimalFelt, + pub transaction_hash: MinimalFelt, + pub max_fee: MinimalFelt, + pub signature: Vec, + pub nonce: MinimalFelt, + pub contract_address_salt: MinimalFelt, + pub constructor_calldata: Vec, + pub class_hash: MinimalFelt, } impl Dummy for DeployAccountTransactionV0V1 { @@ -1708,14 +1855,20 @@ pub(crate) mod dto { constructor_calldata.iter().copied(), &contract_address_salt, &class_hash, - ), + ) + .as_inner() + .to_owned() + .into(), transaction_hash: Faker.fake_with_rng(rng), max_fee: Faker.fake_with_rng(rng), signature: Faker.fake_with_rng(rng), nonce: Faker.fake_with_rng(rng), - contract_address_salt, - constructor_calldata, - class_hash, + contract_address_salt: contract_address_salt.as_inner().to_owned().into(), + constructor_calldata: constructor_calldata + .into_iter() + .map(|x| x.as_inner().to_owned().into()) + .collect(), + class_hash: class_hash.as_inner().to_owned().into(), } } } @@ -1723,19 +1876,19 @@ pub(crate) mod dto { #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct DeployAccountTransactionV3 { - pub nonce: TransactionNonce, + pub nonce: MinimalFelt, pub nonce_data_availability_mode: DataAvailabilityMode, pub fee_data_availability_mode: DataAvailabilityMode, pub resource_bounds: ResourceBounds, pub tip: Tip, - pub paymaster_data: Vec, + pub paymaster_data: Vec, - pub sender_address: ContractAddress, - pub signature: Vec, - pub transaction_hash: TransactionHash, - pub contract_address_salt: ContractAddressSalt, - pub constructor_calldata: Vec, - pub class_hash: ClassHash, + pub sender_address: MinimalFelt, + pub signature: Vec, + pub transaction_hash: MinimalFelt, + pub contract_address_salt: MinimalFelt, + pub constructor_calldata: Vec, + pub class_hash: MinimalFelt, } impl Dummy for DeployAccountTransactionV3 { @@ -1756,12 +1909,18 @@ pub(crate) mod dto { constructor_calldata.iter().copied(), &contract_address_salt, &class_hash, - ), + ) + .as_inner() + .to_owned() + .into(), signature: Faker.fake_with_rng(rng), transaction_hash: Faker.fake_with_rng(rng), - contract_address_salt, - constructor_calldata, - class_hash, + contract_address_salt: contract_address_salt.as_inner().to_owned().into(), + constructor_calldata: constructor_calldata + .into_iter() + .map(|x| x.as_inner().to_owned().into()) + .collect(), + class_hash: class_hash.as_inner().to_owned().into(), } } } @@ -1774,31 +1933,21 @@ pub(crate) mod dto { V3(InvokeTransactionV3), } - impl InvokeTransaction { - pub fn signature(&self) -> &[TransactionSignatureElem] { - match self { - Self::V0(tx) => tx.signature.as_ref(), - Self::V1(tx) => tx.signature.as_ref(), - Self::V3(tx) => tx.signature.as_ref(), - } - } - } - /// Represents deserialized L2 invoke transaction v0 data. #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct InvokeTransactionV0 { - pub calldata: Vec, + pub calldata: Vec, // contract_address is the historic name for this field. sender_address was // introduced with starknet v0.11. Although the gateway no longer uses the historic // name at all, this alias must be kept until a database migration fixes all historic // transaction naming, or until regenesis removes them all. - pub sender_address: ContractAddress, - pub entry_point_selector: EntryPoint, + pub sender_address: MinimalFelt, + pub entry_point_selector: MinimalFelt, pub entry_point_type: Option, - pub max_fee: Fee, - pub signature: Vec, - pub transaction_hash: TransactionHash, + pub max_fee: MinimalFelt, + pub signature: Vec, + pub transaction_hash: MinimalFelt, } impl Dummy for InvokeTransactionV0 { @@ -1819,36 +1968,36 @@ pub(crate) mod dto { #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] #[serde(deny_unknown_fields)] pub struct InvokeTransactionV1 { - pub calldata: Vec, + pub calldata: Vec, // contract_address is the historic name for this field. sender_address was // introduced with starknet v0.11. Although the gateway no longer uses the historic // name at all, this alias must be kept until a database migration fixes all historic // transaction naming, or until regenesis removes them all. #[serde(alias = "contract_address")] - pub sender_address: ContractAddress, - pub max_fee: Fee, - pub signature: Vec, - pub nonce: TransactionNonce, - pub transaction_hash: TransactionHash, + pub sender_address: MinimalFelt, + pub max_fee: MinimalFelt, + pub signature: Vec, + pub nonce: MinimalFelt, + pub transaction_hash: MinimalFelt, } /// Represents deserialized L2 invoke transaction v3 data. #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct InvokeTransactionV3 { - pub nonce: TransactionNonce, + pub nonce: MinimalFelt, pub nonce_data_availability_mode: DataAvailabilityMode, pub fee_data_availability_mode: DataAvailabilityMode, pub resource_bounds: ResourceBounds, pub tip: Tip, - pub paymaster_data: Vec, + pub paymaster_data: Vec, - pub sender_address: ContractAddress, - pub signature: Vec, - pub transaction_hash: TransactionHash, - pub calldata: Vec, + pub sender_address: MinimalFelt, + pub signature: Vec, + pub transaction_hash: MinimalFelt, + pub calldata: Vec, - pub account_deployment_data: Vec, + pub account_deployment_data: Vec, } impl Dummy for InvokeTransactionV3 { @@ -1874,21 +2023,21 @@ pub(crate) mod dto { #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct L1HandlerTransaction { - pub contract_address: ContractAddress, - pub entry_point_selector: EntryPoint, + pub contract_address: MinimalFelt, + pub entry_point_selector: MinimalFelt, // FIXME: remove once starkware fixes their gateway bug which was missing this field. #[serde(default)] - pub nonce: TransactionNonce, - pub calldata: Vec, - pub transaction_hash: TransactionHash, - pub version: TransactionVersion, + pub nonce: MinimalFelt, + pub calldata: Vec, + pub transaction_hash: MinimalFelt, + pub version: MinimalFelt, } impl Dummy for L1HandlerTransaction { fn dummy_with_rng(_: &T, rng: &mut R) -> Self { Self { // TODO verify this is the only realistic value - version: TransactionVersion::ZERO, + version: TransactionVersion::ZERO.0.into(), contract_address: Faker.fake_with_rng(rng), entry_point_selector: Faker.fake_with_rng(rng), diff --git a/crates/storage/src/fake.rs b/crates/storage/src/fake.rs index b70096caa2..e5f1354fc8 100644 --- a/crates/storage/src/fake.rs +++ b/crates/storage/src/fake.rs @@ -158,7 +158,7 @@ pub mod init { let r: Receipt = crate::connection::transaction::dto::Receipt::V0( crate::connection::transaction::dto::ReceiptV0 { - transaction_hash, + transaction_hash: transaction_hash.as_inner().to_owned().into(), transaction_index: TransactionIndex::new_or_panic( i.try_into().expect("u64 is at least as wide as usize"), ), diff --git a/crates/storage/src/schema/revision_0051.rs b/crates/storage/src/schema/revision_0051.rs index 62a0bece5a..cf5b0daab4 100644 --- a/crates/storage/src/schema/revision_0051.rs +++ b/crates/storage/src/schema/revision_0051.rs @@ -52,7 +52,9 @@ pub(crate) fn migrate(tx: &rusqlite::Transaction<'_>) -> anyhow::Result<()> { .context("Compressing receipt") .unwrap(); let events = bincode::serde::encode_to_vec( - crate::transaction::dto::Events::V0 { events }, + crate::transaction::dto::Events::V0 { + events: events.into_iter().map(Into::into).collect(), + }, bincode::config::standard(), ) .context("Serializing events") From 4966fa731ba90d7e14c7948905659e3aa0fa9020 Mon Sep 17 00:00:00 2001 From: sistemd Date: Mon, 18 Mar 2024 15:55:19 +0100 Subject: [PATCH 09/24] progress logging --- crates/storage/src/schema/revision_0051.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/storage/src/schema/revision_0051.rs b/crates/storage/src/schema/revision_0051.rs index cf5b0daab4..e4c65e9349 100644 --- a/crates/storage/src/schema/revision_0051.rs +++ b/crates/storage/src/schema/revision_0051.rs @@ -1,4 +1,9 @@ -use std::{mem, sync::mpsc, thread}; +use std::{ + mem, + sync::mpsc, + thread, + time::{Duration, Instant}, +}; use anyhow::Context; use rusqlite::params; @@ -75,6 +80,9 @@ pub(crate) fn migrate(tx: &rusqlite::Transaction<'_>) -> anyhow::Result<()> { transformers.push(transformer); } + let mut progress_logged = Instant::now(); + const LOG_RATE: Duration = Duration::from_secs(10); + tx.execute( r" CREATE TABLE starknet_transactions_new ( @@ -97,6 +105,11 @@ pub(crate) fn migrate(tx: &rusqlite::Transaction<'_>) -> anyhow::Result<()> { const BATCH_SIZE: usize = 10_000; let mut rows = query_stmt.query([])?; loop { + if progress_logged.elapsed() > LOG_RATE { + progress_logged = Instant::now(); + tracing::info!("Migration still in progress..."); + } + let mut batch_size = 0; for _ in 0..BATCH_SIZE { match rows.next() { From 5a102cbae621ece4ed58bb76e043f8f7b7b0203a Mon Sep 17 00:00:00 2001 From: sistemd Date: Tue, 19 Mar 2024 14:53:36 +0100 Subject: [PATCH 10/24] fixes and qol improvements --- crates/common/src/lib.rs | 28 +-- .../src/p2p_network/sync_handlers/conv.rs | 4 + crates/storage/src/connection/transaction.rs | 168 +++++++++--------- crates/storage/src/schema/revision_0051.rs | 21 ++- 4 files changed, 119 insertions(+), 102 deletions(-) diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index 07624df5f0..405b777d50 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -472,40 +472,40 @@ impl From for StarknetVersion { macros::felt_newtypes!( [ + AccountDeploymentDataElem, BlockHash, ByteCodeOffset, BlockCommitmentSignatureElem, + CallParam, CallResultValue, ClassCommitment, ClassCommitmentLeafHash, + ClassHash, + ConstructorParam, + ContractAddressSalt, ContractNonce, ContractStateHash, ContractRoot, + EntryPoint, EventCommitment, + EventData, + EventKey, + Fee, L1ToL2MessageNonce, L1ToL2MessagePayloadElem, + L2ToL1MessagePayloadElem, + PaymasterDataElem, SequencerAddress, StateCommitment, StateDiffCommitment, StorageCommitment, StorageValue, TransactionCommitment, + TransactionHash, + TransactionNonce, + TransactionSignatureElem, ]; [ - EntryPoint, - CallParam, - ConstructorParam, - ContractAddressSalt, - AccountDeploymentDataElem, - PaymasterDataElem, - TransactionSignatureElem, - TransactionNonce, - ClassHash, - EventKey, - EventData, - TransactionHash, - Fee, - L2ToL1MessagePayloadElem, ContractAddress, SierraHash, CasmHash, diff --git a/crates/pathfinder/src/p2p_network/sync_handlers/conv.rs b/crates/pathfinder/src/p2p_network/sync_handlers/conv.rs index d47eb7b4ce..7456e073fd 100644 --- a/crates/pathfinder/src/p2p_network/sync_handlers/conv.rs +++ b/crates/pathfinder/src/p2p_network/sync_handlers/conv.rs @@ -13,6 +13,7 @@ use pathfinder_common::transaction::DataAvailabilityMode; use pathfinder_common::{event::Event, transaction::ResourceBound, transaction::Transaction}; use pathfinder_common::{ AccountDeploymentDataElem, L1DataAvailabilityMode, PaymasterDataElem, TransactionHash, + TransactionVersion, }; use pathfinder_crypto::Felt; use starknet_gateway_types::class_definition::{Cairo, Sierra}; @@ -92,6 +93,9 @@ impl ToDto for Transaction { // Only these two values are allowed in storage version: if x.version.is_zero() { 0 } else { 1 }, }), + DeployAccountV0V1(x) if x.version == TransactionVersion::ZERO => { + panic!("version zero") + } DeployAccountV0V1(x) => { proto::TransactionVariant::DeployAccountV1(proto::DeployAccountV1 { max_fee: x.max_fee.0, diff --git a/crates/storage/src/connection/transaction.rs b/crates/storage/src/connection/transaction.rs index 776883652c..37ebdafe37 100644 --- a/crates/storage/src/connection/transaction.rs +++ b/crates/storage/src/connection/transaction.rs @@ -36,7 +36,6 @@ pub(super) fn insert_transactions( { // Serialize and compress transaction data. let transaction = dto::Transaction::from(transaction); - let tx_data = bincode::serde::encode_to_vec(&transaction, bincode::config::standard()) .context("Serializing transaction")?; let tx_data = compressor @@ -592,14 +591,10 @@ pub(crate) mod dto { data: value .data .into_iter() - .map(|x| EventData::new_or_panic(x.into())) + .map(|x| EventData(x.into())) .collect(), from_address: ContractAddress::new_or_panic(value.from_address.into()), - keys: value - .keys - .into_iter() - .map(|x| EventKey::new_or_panic(x.into())) - .collect(), + keys: value.keys.into_iter().map(|x| EventKey(x.into())).collect(), } } } @@ -774,7 +769,7 @@ pub(crate) mod dto { from_address: ContractAddress::new_or_panic(from_address.into()), payload: payload .into_iter() - .map(|x| L2ToL1MessagePayloadElem::new_or_panic(x.into())) + .map(|x| L2ToL1MessagePayloadElem(x.into())) .collect(), to_address, } @@ -849,10 +844,10 @@ pub(crate) mod dto { }) = value; common::Receipt { - actual_fee: actual_fee.map(|x| Fee::new_or_panic(x.into())), + actual_fee: actual_fee.map(|x| Fee(x.into())), execution_resources: (&execution_resources.unwrap_or_default()).into(), l2_to_l1_messages: l2_to_l1_messages.into_iter().map(Into::into).collect(), - transaction_hash: TransactionHash::new_or_panic(transaction_hash.into()), + transaction_hash: TransactionHash(transaction_hash.into()), transaction_index, execution_status: match execution_status { ExecutionStatus::Succeeded => common::ExecutionStatus::Succeeded, @@ -1337,13 +1332,13 @@ pub(crate) mod dto { }, ))) => TransactionVariant::DeclareV0( pathfinder_common::transaction::DeclareTransactionV0V1 { - class_hash: ClassHash::new_or_panic(class_hash.into()), - max_fee: Fee::new_or_panic(max_fee.into()), - nonce: TransactionNonce::new_or_panic(nonce.into()), + class_hash: ClassHash(class_hash.into()), + max_fee: Fee(max_fee.into()), + nonce: TransactionNonce(nonce.into()), sender_address: ContractAddress::new_or_panic(sender_address.into()), signature: signature .into_iter() - .map(|x| TransactionSignatureElem::new_or_panic(x.into())) + .map(|x| TransactionSignatureElem(x.into())) .collect(), }, ), @@ -1358,13 +1353,13 @@ pub(crate) mod dto { }, ))) => TransactionVariant::DeclareV1( pathfinder_common::transaction::DeclareTransactionV0V1 { - class_hash: ClassHash::new_or_panic(class_hash.into()), - max_fee: Fee::new_or_panic(max_fee.into()), - nonce: TransactionNonce::new_or_panic(nonce.into()), - sender_address: ContractAddress::new_or_panic(sender_address.into()), + class_hash: ClassHash(class_hash.into()), + max_fee: Fee(max_fee.into()), + nonce: TransactionNonce(nonce.into()), + sender_address: ContractAddress(sender_address.into()), signature: signature .into_iter() - .map(|x| TransactionSignatureElem::new_or_panic(x.into())) + .map(|x| TransactionSignatureElem(x.into())) .collect(), }, ), @@ -1380,13 +1375,13 @@ pub(crate) mod dto { }, ))) => TransactionVariant::DeclareV2( pathfinder_common::transaction::DeclareTransactionV2 { - class_hash: ClassHash::new_or_panic(class_hash.into()), - max_fee: Fee::new_or_panic(max_fee.into()), - nonce: TransactionNonce::new_or_panic(nonce.into()), - sender_address: ContractAddress::new_or_panic(sender_address.into()), + class_hash: ClassHash(class_hash.into()), + max_fee: Fee(max_fee.into()), + nonce: TransactionNonce(nonce.into()), + sender_address: ContractAddress(sender_address.into()), signature: signature .into_iter() - .map(|x| TransactionSignatureElem::new_or_panic(x.into())) + .map(|x| TransactionSignatureElem(x.into())) .collect(), compiled_class_hash: CasmHash::new_or_panic(compiled_class_hash.into()), }, @@ -1408,25 +1403,25 @@ pub(crate) mod dto { }, ))) => TransactionVariant::DeclareV3( pathfinder_common::transaction::DeclareTransactionV3 { - class_hash: ClassHash::new_or_panic(class_hash.into()), - nonce: TransactionNonce::new_or_panic(nonce.into()), + class_hash: ClassHash(class_hash.into()), + nonce: TransactionNonce(nonce.into()), nonce_data_availability_mode: nonce_data_availability_mode.into(), fee_data_availability_mode: fee_data_availability_mode.into(), resource_bounds: resource_bounds.into(), tip, paymaster_data: paymaster_data .into_iter() - .map(|x| PaymasterDataElem::new_or_panic(x.into())) + .map(|x| PaymasterDataElem(x.into())) .collect(), sender_address: ContractAddress::new_or_panic(sender_address.into()), signature: signature .into_iter() - .map(|x| TransactionSignatureElem::new_or_panic(x.into())) + .map(|x| TransactionSignatureElem(x.into())) .collect(), compiled_class_hash: CasmHash::new_or_panic(compiled_class_hash.into()), account_deployment_data: account_deployment_data .into_iter() - .map(|x| AccountDeploymentDataElem::new_or_panic(x.into())) + .map(|x| AccountDeploymentDataElem(x.into())) .collect(), }, ), @@ -1440,13 +1435,11 @@ pub(crate) mod dto { })) => { TransactionVariant::Deploy(pathfinder_common::transaction::DeployTransaction { contract_address: ContractAddress::new_or_panic(contract_address.into()), - contract_address_salt: ContractAddressSalt::new_or_panic( - contract_address_salt.into(), - ), - class_hash: ClassHash::new_or_panic(class_hash.into()), + contract_address_salt: ContractAddressSalt(contract_address_salt.into()), + class_hash: ClassHash(class_hash.into()), constructor_calldata: constructor_calldata .into_iter() - .map(|x| ConstructorParam::new_or_panic(x.into())) + .map(|x| ConstructorParam(x.into())) .collect(), version: TransactionVersion(version.into()), }) @@ -1465,21 +1458,19 @@ pub(crate) mod dto { ))) => TransactionVariant::DeployAccountV0V1( pathfinder_common::transaction::DeployAccountTransactionV0V1 { contract_address: ContractAddress::new_or_panic(contract_address.into()), - max_fee: Fee::new_or_panic(max_fee.into()), + max_fee: Fee(max_fee.into()), version: TransactionVersion::ZERO, signature: signature .into_iter() - .map(|x| TransactionSignatureElem::new_or_panic(x.into())) + .map(|x| TransactionSignatureElem(x.into())) .collect(), - nonce: TransactionNonce::new_or_panic(nonce.into()), - contract_address_salt: ContractAddressSalt::new_or_panic( - contract_address_salt.into(), - ), + nonce: TransactionNonce(nonce.into()), + contract_address_salt: ContractAddressSalt(contract_address_salt.into()), constructor_calldata: constructor_calldata .into_iter() - .map(|x| CallParam::new_or_panic(x.into())) + .map(|x| CallParam(x.into())) .collect(), - class_hash: ClassHash::new_or_panic(class_hash.into()), + class_hash: ClassHash(class_hash.into()), }, ), Transaction::V0(TransactionV0::DeployAccount(DeployAccountTransaction::V1( @@ -1496,21 +1487,19 @@ pub(crate) mod dto { ))) => TransactionVariant::DeployAccountV0V1( pathfinder_common::transaction::DeployAccountTransactionV0V1 { contract_address: ContractAddress::new_or_panic(contract_address.into()), - max_fee: Fee::new_or_panic(max_fee.into()), + max_fee: Fee(max_fee.into()), version: TransactionVersion::ONE, signature: signature .into_iter() - .map(|x| TransactionSignatureElem::new_or_panic(x.into())) + .map(|x| TransactionSignatureElem(x.into())) .collect(), - nonce: TransactionNonce::new_or_panic(nonce.into()), - contract_address_salt: ContractAddressSalt::new_or_panic( - contract_address_salt.into(), - ), + nonce: TransactionNonce(nonce.into()), + contract_address_salt: ContractAddressSalt(contract_address_salt.into()), constructor_calldata: constructor_calldata .into_iter() - .map(|x| CallParam::new_or_panic(x.into())) + .map(|x| CallParam(x.into())) .collect(), - class_hash: ClassHash::new_or_panic(class_hash.into()), + class_hash: ClassHash(class_hash.into()), }, ), Transaction::V0(TransactionV0::DeployAccount(DeployAccountTransaction::V3( @@ -1533,25 +1522,23 @@ pub(crate) mod dto { contract_address: ContractAddress::new_or_panic(sender_address.into()), signature: signature .into_iter() - .map(|x| TransactionSignatureElem::new_or_panic(x.into())) + .map(|x| TransactionSignatureElem(x.into())) .collect(), - nonce: TransactionNonce::new_or_panic(nonce.into()), + nonce: TransactionNonce(nonce.into()), nonce_data_availability_mode: nonce_data_availability_mode.into(), fee_data_availability_mode: fee_data_availability_mode.into(), resource_bounds: resource_bounds.into(), tip, paymaster_data: paymaster_data .into_iter() - .map(|x| PaymasterDataElem::new_or_panic(x.into())) + .map(|x| PaymasterDataElem(x.into())) .collect(), - contract_address_salt: ContractAddressSalt::new_or_panic( - contract_address_salt.into(), - ), + contract_address_salt: ContractAddressSalt(contract_address_salt.into()), constructor_calldata: constructor_calldata .into_iter() - .map(|x| CallParam::new_or_panic(x.into())) + .map(|x| CallParam(x.into())) .collect(), - class_hash: ClassHash::new_or_panic(class_hash.into()), + class_hash: ClassHash(class_hash.into()), }, ), Transaction::V0(TransactionV0::Invoke(InvokeTransaction::V0( @@ -1566,17 +1553,14 @@ pub(crate) mod dto { }, ))) => TransactionVariant::InvokeV0( pathfinder_common::transaction::InvokeTransactionV0 { - calldata: calldata - .into_iter() - .map(|x| CallParam::new_or_panic(x.into())) - .collect(), + calldata: calldata.into_iter().map(|x| CallParam(x.into())).collect(), sender_address: ContractAddress::new_or_panic(sender_address.into()), - entry_point_selector: EntryPoint::new_or_panic(entry_point_selector.into()), + entry_point_selector: EntryPoint(entry_point_selector.into()), entry_point_type: entry_point_type.map(Into::into), - max_fee: Fee::new_or_panic(max_fee.into()), + max_fee: Fee(max_fee.into()), signature: signature .into_iter() - .map(|x| TransactionSignatureElem::new_or_panic(x.into())) + .map(|x| TransactionSignatureElem(x.into())) .collect(), }, ), @@ -1591,17 +1575,14 @@ pub(crate) mod dto { }, ))) => TransactionVariant::InvokeV1( pathfinder_common::transaction::InvokeTransactionV1 { - calldata: calldata - .into_iter() - .map(|x| CallParam::new_or_panic(x.into())) - .collect(), + calldata: calldata.into_iter().map(|x| CallParam(x.into())).collect(), sender_address: ContractAddress::new_or_panic(sender_address.into()), - max_fee: Fee::new_or_panic(max_fee.into()), + max_fee: Fee(max_fee.into()), signature: signature .into_iter() - .map(|x| TransactionSignatureElem::new_or_panic(x.into())) + .map(|x| TransactionSignatureElem(x.into())) .collect(), - nonce: TransactionNonce::new_or_panic(nonce.into()), + nonce: TransactionNonce(nonce.into()), }, ), Transaction::V0(TransactionV0::Invoke(InvokeTransaction::V3( @@ -1622,25 +1603,22 @@ pub(crate) mod dto { pathfinder_common::transaction::InvokeTransactionV3 { signature: signature .into_iter() - .map(|x| TransactionSignatureElem::new_or_panic(x.into())) + .map(|x| TransactionSignatureElem(x.into())) .collect(), - nonce: TransactionNonce::new_or_panic(nonce.into()), + nonce: TransactionNonce(nonce.into()), nonce_data_availability_mode: nonce_data_availability_mode.into(), fee_data_availability_mode: fee_data_availability_mode.into(), resource_bounds: resource_bounds.into(), tip, paymaster_data: paymaster_data .into_iter() - .map(|x| PaymasterDataElem::new_or_panic(x.into())) + .map(|x| PaymasterDataElem(x.into())) .collect(), account_deployment_data: account_deployment_data .into_iter() - .map(|x| AccountDeploymentDataElem::new_or_panic(x.into())) - .collect(), - calldata: calldata - .into_iter() - .map(|x| CallParam::new_or_panic(x.into())) + .map(|x| AccountDeploymentDataElem(x.into())) .collect(), + calldata: calldata.into_iter().map(|x| CallParam(x.into())).collect(), sender_address: ContractAddress::new_or_panic(sender_address.into()), }, ), @@ -1655,12 +1633,9 @@ pub(crate) mod dto { })) => TransactionVariant::L1Handler( pathfinder_common::transaction::L1HandlerTransaction { contract_address: ContractAddress::new_or_panic(contract_address.into()), - entry_point_selector: EntryPoint::new_or_panic(entry_point_selector.into()), - nonce: TransactionNonce::new_or_panic(nonce.into()), - calldata: calldata - .into_iter() - .map(|x| CallParam::new_or_panic(x.into())) - .collect(), + entry_point_selector: EntryPoint(entry_point_selector.into()), + nonce: TransactionNonce(nonce.into()), + calldata: calldata.into_iter().map(|x| CallParam(x.into())).collect(), }, ), }; @@ -1672,7 +1647,7 @@ pub(crate) mod dto { impl Transaction { /// Returns hash of the transaction pub fn hash(&self) -> TransactionHash { - TransactionHash::new_or_panic( + TransactionHash( match self { Transaction::V0(TransactionV0::Declare(t)) => match t { DeclareTransaction::V0(t) => t.transaction_hash.clone(), @@ -2066,6 +2041,25 @@ mod tests { use super::*; + #[test] + fn serialize_deserialize_transaction() { + let transaction = pathfinder_common::transaction::Transaction { + hash: transaction_hash_bytes!(b"pending tx hash 1"), + variant: TransactionVariant::Deploy(DeployTransaction { + contract_address: contract_address!("0x1122355"), + contract_address_salt: contract_address_salt_bytes!(b"salty"), + class_hash: class_hash_bytes!(b"pending class hash 1"), + version: TransactionVersion::ONE, + ..Default::default() + }), + }; + let dto = dto::Transaction::from(&transaction); + let serialized = bincode::serde::encode_to_vec(&dto, bincode::config::standard()).unwrap(); + let deserialized: (dto::Transaction, _) = + bincode::serde::decode_from_slice(&serialized, bincode::config::standard()).unwrap(); + assert_eq!(deserialized.0, dto); + } + fn setup() -> ( crate::Connection, BlockHeader, diff --git a/crates/storage/src/schema/revision_0051.rs b/crates/storage/src/schema/revision_0051.rs index e4c65e9349..cbb3086f14 100644 --- a/crates/storage/src/schema/revision_0051.rs +++ b/crates/storage/src/schema/revision_0051.rs @@ -38,6 +38,15 @@ pub(crate) fn migrate(tx: &rusqlite::Transaction<'_>) -> anyhow::Result<()> { let events = mem::take(&mut receipt.events); let receipt = pathfinder_common::receipt::Receipt::from(receipt); + if let pathfinder_common::transaction::TransactionVariant::DeployAccountV0V1( + ref t, + ) = transaction.variant + { + if t.version == pathfinder_common::TransactionVersion::ZERO { + panic!("version zero"); + } + } + // Serialize into new DTOs. let transaction = crate::transaction::dto::Transaction::from(&transaction); let transaction = @@ -83,6 +92,9 @@ pub(crate) fn migrate(tx: &rusqlite::Transaction<'_>) -> anyhow::Result<()> { let mut progress_logged = Instant::now(); const LOG_RATE: Duration = Duration::from_secs(10); + let count = tx.query_row("SELECT COUNT(*) FROM starknet_transactions", [], |row| { + row.get::<_, i64>(0) + })?; tx.execute( r" CREATE TABLE starknet_transactions_new ( @@ -104,10 +116,14 @@ pub(crate) fn migrate(tx: &rusqlite::Transaction<'_>) -> anyhow::Result<()> { )?; const BATCH_SIZE: usize = 10_000; let mut rows = query_stmt.query([])?; + let mut progress = 0; loop { if progress_logged.elapsed() > LOG_RATE { progress_logged = Instant::now(); - tracing::info!("Migration still in progress..."); + tracing::info!( + "Migrating rows: {:.2}%", + (progress as f64 / count as f64) * 100.0 + ); } let mut batch_size = 0; @@ -137,6 +153,7 @@ pub(crate) fn migrate(tx: &rusqlite::Transaction<'_>) -> anyhow::Result<()> { for _ in 0..batch_size { let (hash, idx, block_hash, transaction, receipt, events) = insert_rx.recv()?; insert_stmt.execute(params![hash, idx, block_hash, transaction, receipt, events])?; + progress += 1; } if batch_size < BATCH_SIZE { // This was the last batch. @@ -152,7 +169,9 @@ pub(crate) fn migrate(tx: &rusqlite::Transaction<'_>) -> anyhow::Result<()> { transformer.join().unwrap(); } + tracing::info!("Dropping old starknet_transactions table"); tx.execute("DROP TABLE starknet_transactions", [])?; + tracing::info!("Renaming starknet_transactions_new to starknet_transactions"); tx.execute( "ALTER TABLE starknet_transactions_new RENAME TO starknet_transactions", [], From 6f3c3d056491697fe18437186342244b8d30339a Mon Sep 17 00:00:00 2001 From: sistemd Date: Wed, 20 Mar 2024 17:42:39 +0100 Subject: [PATCH 11/24] remove deploy account v0 --- crates/common/src/transaction.rs | 25 ++--- crates/gateway-types/src/reply.rs | 35 ++++--- crates/p2p/src/client/conv.rs | 5 +- .../src/p2p_network/sync_handlers/conv.rs | 8 +- .../src/p2p_network/sync_handlers/tests.rs | 2 +- crates/pathfinder/src/state/block_hash.rs | 4 +- crates/rpc/src/executor.rs | 6 +- crates/rpc/src/v02/types.rs | 29 ++++-- .../src/v04/method/get_transaction_receipt.rs | 4 +- crates/rpc/src/v04/types/transaction.rs | 20 ++-- .../v05/method/trace_block_transactions.rs | 2 +- .../src/v06/method/get_transaction_receipt.rs | 4 +- .../v06/method/trace_block_transactions.rs | 2 +- crates/rpc/src/v06/types/transaction.rs | 18 ++-- crates/rpc/src/v07/dto/receipt.rs | 4 +- crates/storage/src/connection/transaction.rs | 92 +++---------------- crates/storage/src/schema/revision_0051.rs | 44 ++++----- 17 files changed, 115 insertions(+), 189 deletions(-) diff --git a/crates/common/src/transaction.rs b/crates/common/src/transaction.rs index 003f32f710..670825e9d5 100644 --- a/crates/common/src/transaction.rs +++ b/crates/common/src/transaction.rs @@ -29,7 +29,7 @@ impl Transaction { TransactionVariant::DeclareV2(_) => TransactionVersion::TWO, TransactionVariant::DeclareV3(_) => TransactionVersion::THREE, TransactionVariant::Deploy(tx) => tx.version, - TransactionVariant::DeployAccountV0V1(tx) => tx.version, + TransactionVariant::DeployAccountV1(_) => TransactionVersion::ONE, TransactionVariant::DeployAccountV3(_) => TransactionVersion::THREE, TransactionVariant::InvokeV0(_) => TransactionVersion::ZERO, TransactionVariant::InvokeV1(_) => TransactionVersion::ONE, @@ -46,10 +46,7 @@ pub enum TransactionVariant { DeclareV2(DeclareTransactionV2), DeclareV3(DeclareTransactionV3), Deploy(DeployTransaction), - // FIXME: This should get separated into v0 and v1 variants. - // Currently this allows for ambiguity as version is - // flexible. - DeployAccountV0V1(DeployAccountTransactionV0V1), + DeployAccountV1(DeployAccountTransactionV1), DeployAccountV3(DeployAccountTransactionV3), InvokeV0(InvokeTransactionV0), InvokeV1(InvokeTransactionV1), @@ -86,7 +83,7 @@ impl TransactionVariant { TransactionVariant::DeclareV2(tx) => tx.calculate_hash(chain_id, query_only), TransactionVariant::DeclareV3(tx) => tx.calculate_hash(chain_id, query_only), TransactionVariant::Deploy(tx) => tx.calculate_hash(chain_id), - TransactionVariant::DeployAccountV0V1(tx) => tx.calculate_hash(chain_id, query_only), + TransactionVariant::DeployAccountV1(tx) => tx.calculate_hash(chain_id, query_only), TransactionVariant::DeployAccountV3(tx) => tx.calculate_hash(chain_id, query_only), TransactionVariant::InvokeV0(tx) => tx.calculate_hash(chain_id, query_only), TransactionVariant::InvokeV1(tx) => tx.calculate_hash(chain_id, query_only), @@ -124,9 +121,9 @@ impl From for TransactionVariant { Self::Deploy(value) } } -impl From for TransactionVariant { - fn from(value: DeployAccountTransactionV0V1) -> Self { - Self::DeployAccountV0V1(value) +impl From for TransactionVariant { + fn from(value: DeployAccountTransactionV1) -> Self { + Self::DeployAccountV1(value) } } impl From for TransactionVariant { @@ -199,10 +196,9 @@ pub struct DeployTransaction { } #[derive(Clone, Default, Debug, PartialEq, Eq)] -pub struct DeployAccountTransactionV0V1 { +pub struct DeployAccountTransactionV1 { pub contract_address: ContractAddress, pub max_fee: Fee, - pub version: TransactionVersion, pub signature: Vec, pub nonce: TransactionNonce, pub contract_address_salt: ContractAddressSalt, @@ -377,7 +373,7 @@ impl DeployTransaction { } } -impl DeployAccountTransactionV0V1 { +impl DeployAccountTransactionV1 { fn calculate_hash(&self, chain_id: ChainId, query_only: bool) -> TransactionHash { let constructor_calldata_hash = std::iter::once(self.class_hash.0) .chain(std::iter::once(self.contract_address_salt.0)) @@ -389,7 +385,7 @@ impl DeployAccountTransactionV0V1 { PreV3Hasher { prefix: felt_bytes!(b"deploy_account"), - version: self.version.with_query_only(query_only), + version: TransactionVersion::ONE.with_query_only(query_only), address: self.contract_address, data_hash: constructor_calldata_hash, max_fee: self.max_fee, @@ -973,12 +969,11 @@ mod tests { hash: transaction_hash!( "0x63b72dba5a1b5cdd2585b0c7103242244860453f7013023c1a21f32e1863ec" ), - variant: TransactionVariant::DeployAccountV0V1(DeployAccountTransactionV0V1 { + variant: TransactionVariant::DeployAccountV1(DeployAccountTransactionV1 { contract_address: contract_address!( "0x3faed8332496d9de9c546e7942b35ba3ea323a6af72d6033f746ea60ecc02ef" ), max_fee: fee!("0xb48040809d4b"), - version: TransactionVersion::ONE, signature: vec![ transaction_signature_elem!( "0x463d21c552a810c59be86c336c0cc68f28e3815eafbe1a2eaf9b3a6fe1c2b82" diff --git a/crates/gateway-types/src/reply.rs b/crates/gateway-types/src/reply.rs index f61b7e55ff..d9a317c190 100644 --- a/crates/gateway-types/src/reply.rs +++ b/crates/gateway-types/src/reply.rs @@ -1048,10 +1048,9 @@ pub(crate) mod transaction { transaction_hash, version, }), - DeployAccountV0V1(DeployAccountTransactionV0V1 { + DeployAccountV1(DeployAccountTransactionV1 { contract_address, max_fee, - version, signature, nonce, contract_address_salt, @@ -1062,7 +1061,7 @@ pub(crate) mod transaction { contract_address, transaction_hash, max_fee, - version, + version: TransactionVersion::ONE, signature, nonce, contract_address_salt, @@ -1282,18 +1281,24 @@ pub(crate) mod transaction { constructor_calldata, class_hash, }, - )) => TransactionVariant::DeployAccountV0V1( - pathfinder_common::transaction::DeployAccountTransactionV0V1 { - contract_address, - max_fee, - version, - signature, - nonce, - contract_address_salt, - constructor_calldata, - class_hash, - }, - ), + )) if version.without_query_version() + == TransactionVersion::ONE.without_query_version() => + { + TransactionVariant::DeployAccountV1( + pathfinder_common::transaction::DeployAccountTransactionV1 { + contract_address, + max_fee, + signature, + nonce, + contract_address_salt, + constructor_calldata, + class_hash, + }, + ) + } + Transaction::DeployAccount(DeployAccountTransaction::V0V1( + DeployAccountTransactionV0V1 { version, .. }, + )) => panic!("unexpected deploy account transaction version {version:?}"), Transaction::DeployAccount(DeployAccountTransaction::V3( DeployAccountTransactionV3 { nonce, diff --git a/crates/p2p/src/client/conv.rs b/crates/p2p/src/client/conv.rs index 032aa3cb51..a02ef5f9fd 100644 --- a/crates/p2p/src/client/conv.rs +++ b/crates/p2p/src/client/conv.rs @@ -6,7 +6,7 @@ use pathfinder_common::{ state_update::StateUpdateCounts, transaction::{ DataAvailabilityMode, DeclareTransactionV0V1, DeclareTransactionV2, DeclareTransactionV3, - DeployAccountTransactionV0V1, DeployAccountTransactionV3, DeployTransaction, + DeployAccountTransactionV1, DeployAccountTransactionV3, DeployTransaction, InvokeTransactionV0, InvokeTransactionV1, InvokeTransactionV3, L1HandlerTransaction, ResourceBound, ResourceBounds, Transaction, TransactionVariant, }, @@ -172,10 +172,9 @@ impl TryFromDto for TransactionVaria _ => anyhow::bail!("Invalid deploy transaction version"), }, }), - DeployAccountV1(x) => Self::DeployAccountV0V1(DeployAccountTransactionV0V1 { + DeployAccountV1(x) => Self::DeployAccountV1(DeployAccountTransactionV1 { contract_address: ContractAddress::ZERO, max_fee: Fee(x.max_fee), - version: TransactionVersion::ONE, signature: x .signature .parts diff --git a/crates/pathfinder/src/p2p_network/sync_handlers/conv.rs b/crates/pathfinder/src/p2p_network/sync_handlers/conv.rs index 7456e073fd..2d78bb2a1d 100644 --- a/crates/pathfinder/src/p2p_network/sync_handlers/conv.rs +++ b/crates/pathfinder/src/p2p_network/sync_handlers/conv.rs @@ -13,7 +13,6 @@ use pathfinder_common::transaction::DataAvailabilityMode; use pathfinder_common::{event::Event, transaction::ResourceBound, transaction::Transaction}; use pathfinder_common::{ AccountDeploymentDataElem, L1DataAvailabilityMode, PaymasterDataElem, TransactionHash, - TransactionVersion, }; use pathfinder_crypto::Felt; use starknet_gateway_types::class_definition::{Cairo, Sierra}; @@ -93,10 +92,7 @@ impl ToDto for Transaction { // Only these two values are allowed in storage version: if x.version.is_zero() { 0 } else { 1 }, }), - DeployAccountV0V1(x) if x.version == TransactionVersion::ZERO => { - panic!("version zero") - } - DeployAccountV0V1(x) => { + DeployAccountV1(x) => { proto::TransactionVariant::DeployAccountV1(proto::DeployAccountV1 { max_fee: x.max_fee.0, signature: AccountSignature { @@ -247,7 +243,7 @@ impl ToDto for (Transaction, Receipt) { common, contract_address: x.contract_address.0, }), - TransactionVariant::DeployAccountV0V1(x) => { + TransactionVariant::DeployAccountV1(x) => { DeployAccount(DeployAccountTransactionReceipt { common, contract_address: x.contract_address.0, diff --git a/crates/pathfinder/src/p2p_network/sync_handlers/tests.rs b/crates/pathfinder/src/p2p_network/sync_handlers/tests.rs index 9c008db74c..b346aa5b7e 100644 --- a/crates/pathfinder/src/p2p_network/sync_handlers/tests.rs +++ b/crates/pathfinder/src/p2p_network/sync_handlers/tests.rs @@ -375,7 +375,7 @@ mod prop { // P2P transactions don't carry contract address, so zero them just like `try_from_dto` does match &mut txn.variant { TransactionVariant::Deploy(x) => x.contract_address = ContractAddress::ZERO, - TransactionVariant::DeployAccountV0V1(x) => x.contract_address = ContractAddress::ZERO, + TransactionVariant::DeployAccountV1(x) => x.contract_address = ContractAddress::ZERO, TransactionVariant::DeployAccountV3(x) => x.contract_address = ContractAddress::ZERO, _ => {} }; diff --git a/crates/pathfinder/src/state/block_hash.rs b/crates/pathfinder/src/state/block_hash.rs index 3124177134..a047c1b969 100644 --- a/crates/pathfinder/src/state/block_hash.rs +++ b/crates/pathfinder/src/state/block_hash.rs @@ -381,7 +381,7 @@ fn calculate_transaction_hash_with_signature_pre_0_11_1(tx: &Transaction) -> Fel | TransactionVariant::DeclareV2(_) | TransactionVariant::DeclareV3(_) | TransactionVariant::Deploy(_) - | TransactionVariant::DeployAccountV0V1(_) + | TransactionVariant::DeployAccountV1(_) | TransactionVariant::DeployAccountV3(_) | TransactionVariant::L1Handler(_) => *HASH_OF_EMPTY_LIST, }; @@ -409,7 +409,7 @@ fn calculate_transaction_hash_with_signature(tx: &Transaction) -> Felt { TransactionVariant::DeclareV1(tx) => calculate_signature_hash(&tx.signature), TransactionVariant::DeclareV2(tx) => calculate_signature_hash(&tx.signature), TransactionVariant::DeclareV3(tx) => calculate_signature_hash(&tx.signature), - TransactionVariant::DeployAccountV0V1(tx) => calculate_signature_hash(&tx.signature), + TransactionVariant::DeployAccountV1(tx) => calculate_signature_hash(&tx.signature), TransactionVariant::DeployAccountV3(tx) => calculate_signature_hash(&tx.signature), TransactionVariant::InvokeV1(tx) => calculate_signature_hash(&tx.signature), TransactionVariant::InvokeV3(tx) => calculate_signature_hash(&tx.signature), diff --git a/crates/rpc/src/executor.rs b/crates/rpc/src/executor.rs index 10da6dddec..b8e3f77dbf 100644 --- a/crates/rpc/src/executor.rs +++ b/crates/rpc/src/executor.rs @@ -264,7 +264,7 @@ fn map_transaction_variant( TransactionVariant::Deploy(_) => { anyhow::bail!("Deploy transactions are not yet supported in blockifier") } - TransactionVariant::DeployAccountV0V1(tx) => { + TransactionVariant::DeployAccountV1(tx) => { let tx = starknet_api::transaction::DeployAccountTransaction::V1( starknet_api::transaction::DeployAccountTransactionV1 { max_fee: starknet_api::transaction::Fee(u128::from_be_bytes( @@ -506,7 +506,7 @@ pub fn compose_executor_transaction( )?) } TransactionVariant::Deploy(_) - | TransactionVariant::DeployAccountV0V1(_) + | TransactionVariant::DeployAccountV1(_) | TransactionVariant::DeployAccountV3(_) | TransactionVariant::InvokeV0(_) | TransactionVariant::InvokeV1(_) @@ -515,7 +515,7 @@ pub fn compose_executor_transaction( }; let deployed_address = match &transaction.variant { - TransactionVariant::DeployAccountV0V1(tx) => { + TransactionVariant::DeployAccountV1(tx) => { let contract_address = starknet_api::core::ContractAddress( PatriciaKey::try_from(tx.contract_address.get().into_starkfelt()) .expect("No contract address overflow expected"), diff --git a/crates/rpc/src/v02/types.rs b/crates/rpc/src/v02/types.rs index f1df0febe5..89d50b3dce 100644 --- a/crates/rpc/src/v02/types.rs +++ b/crates/rpc/src/v02/types.rs @@ -528,16 +528,25 @@ pub mod request { } BroadcastedTransaction::DeployAccount( BroadcastedDeployAccountTransaction::V0V1(deploy), - ) => TransactionVariant::DeployAccountV0V1(DeployAccountTransactionV0V1 { - contract_address: deploy.deployed_contract_address(), - max_fee: deploy.max_fee, - version: deploy.version, - signature: deploy.signature, - nonce: deploy.nonce, - contract_address_salt: deploy.contract_address_salt, - constructor_calldata: deploy.constructor_calldata, - class_hash: deploy.class_hash, - }), + ) if deploy.version.without_query_version() + == TransactionVersion::ONE.without_query_version() => + { + TransactionVariant::DeployAccountV1(DeployAccountTransactionV1 { + contract_address: deploy.deployed_contract_address(), + max_fee: deploy.max_fee, + signature: deploy.signature, + nonce: deploy.nonce, + contract_address_salt: deploy.contract_address_salt, + constructor_calldata: deploy.constructor_calldata, + class_hash: deploy.class_hash, + }) + } + BroadcastedTransaction::DeployAccount( + BroadcastedDeployAccountTransaction::V0V1(deploy), + ) => panic!( + "deploy account transaction version {:?} is not supported", + deploy.version + ), BroadcastedTransaction::DeployAccount(BroadcastedDeployAccountTransaction::V3( deploy, )) => TransactionVariant::DeployAccountV3(DeployAccountTransactionV3 { diff --git a/crates/rpc/src/v04/method/get_transaction_receipt.rs b/crates/rpc/src/v04/method/get_transaction_receipt.rs index c4d65c97e9..eac7f99fb1 100644 --- a/crates/rpc/src/v04/method/get_transaction_receipt.rs +++ b/crates/rpc/src/v04/method/get_transaction_receipt.rs @@ -242,7 +242,7 @@ pub mod types { common, contract_address: tx.contract_address, }), - TransactionVariant::DeployAccountV0V1(tx) => { + TransactionVariant::DeployAccountV1(tx) => { Self::DeployAccount(DeployAccountTransactionReceipt { common, contract_address: tx.contract_address, @@ -373,7 +373,7 @@ pub mod types { common, contract_address: tx.contract_address, }), - TransactionVariant::DeployAccountV0V1(tx) => { + TransactionVariant::DeployAccountV1(tx) => { Self::DeployAccount(PendingDeployAccountTransactionReceipt { common, contract_address: tx.contract_address, diff --git a/crates/rpc/src/v04/types/transaction.rs b/crates/rpc/src/v04/types/transaction.rs index dce558817c..5944d1d58d 100644 --- a/crates/rpc/src/v04/types/transaction.rs +++ b/crates/rpc/src/v04/types/transaction.rs @@ -1,8 +1,7 @@ use pathfinder_common::transaction::{ - DeclareTransactionV0V1, DeclareTransactionV2, DeclareTransactionV3, - DeployAccountTransactionV0V1, DeployAccountTransactionV3, DeployTransaction, - InvokeTransactionV0, InvokeTransactionV1, InvokeTransactionV3, L1HandlerTransaction, - ResourceBounds, + DeclareTransactionV0V1, DeclareTransactionV2, DeclareTransactionV3, DeployAccountTransactionV1, + DeployAccountTransactionV3, DeployTransaction, InvokeTransactionV0, InvokeTransactionV1, + InvokeTransactionV3, L1HandlerTransaction, ResourceBounds, }; use pathfinder_common::{Fee, TransactionHash, TransactionVersion}; use pathfinder_crypto::Felt; @@ -42,8 +41,8 @@ impl Serialize for Transaction { TransactionVariant::DeclareV2(x) => DeclareV2Helper(x).serialize(serializer), TransactionVariant::DeclareV3(x) => DeclareV3MapToV2Helper(x).serialize(serializer), TransactionVariant::Deploy(x) => DeployHelper(x).serialize(serializer), - TransactionVariant::DeployAccountV0V1(x) => { - DeployAccountV0V1Helper(x).serialize(serializer) + TransactionVariant::DeployAccountV1(x) => { + DeployAccountV1Helper(x).serialize(serializer) } TransactionVariant::DeployAccountV3(x) => { DeployAccountV3MapToV1Helper(x).serialize(serializer) @@ -61,7 +60,7 @@ struct DeclareV1Helper<'a>(&'a DeclareTransactionV0V1); struct DeclareV2Helper<'a>(&'a DeclareTransactionV2); struct DeclareV3MapToV2Helper<'a>(&'a DeclareTransactionV3); struct DeployHelper<'a>(&'a DeployTransaction); -struct DeployAccountV0V1Helper<'a>(&'a DeployAccountTransactionV0V1); +struct DeployAccountV1Helper<'a>(&'a DeployAccountTransactionV1); struct DeployAccountV3MapToV1Helper<'a>(&'a DeployAccountTransactionV3); struct InvokeV0Helper<'a>(&'a InvokeTransactionV0); struct InvokeV1Helper<'a>(&'a InvokeTransactionV1); @@ -155,7 +154,7 @@ impl Serialize for DeployHelper<'_> { } } -impl Serialize for DeployAccountV0V1Helper<'_> { +impl Serialize for DeployAccountV1Helper<'_> { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -163,7 +162,7 @@ impl Serialize for DeployAccountV0V1Helper<'_> { let mut s = serializer.serialize_struct("DeployAccount", 8)?; s.serialize_field("type", "DEPLOY_ACCOUNT")?; s.serialize_field("max_fee", &self.0.max_fee)?; - s.serialize_field("version", &TransactionVersionHelper(&self.0.version))?; + s.serialize_field("version", "0x1")?; s.serialize_field("signature", &self.0.signature)?; s.serialize_field("nonce", &self.0.nonce)?; s.serialize_field("contract_address_salt", &self.0.contract_address_salt)?; @@ -455,10 +454,9 @@ mod tests { #[test] fn deploy_account_v1() { - let original: TransactionVariant = DeployAccountTransactionV0V1 { + let original: TransactionVariant = DeployAccountTransactionV1 { contract_address: contract_address!("0xabc"), max_fee: fee!("0x1111"), - version: TransactionVersion::ONE, signature: vec![ transaction_signature_elem!("0xa1b1"), transaction_signature_elem!("0x1a1b"), diff --git a/crates/rpc/src/v05/method/trace_block_transactions.rs b/crates/rpc/src/v05/method/trace_block_transactions.rs index 57ce02c563..883acbb484 100644 --- a/crates/rpc/src/v05/method/trace_block_transactions.rs +++ b/crates/rpc/src/v05/method/trace_block_transactions.rs @@ -102,7 +102,7 @@ pub(crate) fn map_gateway_trace( validate_invocation: trace.validate_invocation.map(Into::into), state_diff: None, }), - TransactionVariant::DeployAccountV0V1(_) | TransactionVariant::DeployAccountV3(_) => { + TransactionVariant::DeployAccountV1(_) | TransactionVariant::DeployAccountV3(_) => { TransactionTrace::DeployAccount(DeployAccountTxnTrace { constructor_invocation: trace.function_invocation.map(Into::into), fee_transfer_invocation: trace.fee_transfer_invocation.map(Into::into), diff --git a/crates/rpc/src/v06/method/get_transaction_receipt.rs b/crates/rpc/src/v06/method/get_transaction_receipt.rs index ca04f6c49e..48a156273b 100644 --- a/crates/rpc/src/v06/method/get_transaction_receipt.rs +++ b/crates/rpc/src/v06/method/get_transaction_receipt.rs @@ -509,7 +509,7 @@ pub mod types { common, contract_address: tx.contract_address, }), - TransactionVariant::DeployAccountV0V1(tx) => { + TransactionVariant::DeployAccountV1(tx) => { Self::DeployAccount(DeployAccountTransactionReceipt { common, contract_address: tx.contract_address, @@ -649,7 +649,7 @@ pub mod types { common, contract_address: tx.contract_address, }), - TransactionVariant::DeployAccountV0V1(tx) => { + TransactionVariant::DeployAccountV1(tx) => { Self::DeployAccount(PendingDeployAccountTransactionReceipt { common, contract_address: tx.contract_address, diff --git a/crates/rpc/src/v06/method/trace_block_transactions.rs b/crates/rpc/src/v06/method/trace_block_transactions.rs index b5bd2c5645..ef46a36600 100644 --- a/crates/rpc/src/v06/method/trace_block_transactions.rs +++ b/crates/rpc/src/v06/method/trace_block_transactions.rs @@ -128,7 +128,7 @@ pub(crate) fn map_gateway_trace( state_diff, execution_resources, }), - TransactionVariant::DeployAccountV0V1(_) + TransactionVariant::DeployAccountV1(_) | TransactionVariant::DeployAccountV3(_) | TransactionVariant::Deploy(_) => TransactionTrace::DeployAccount(DeployAccountTxnTrace { constructor_invocation: function_invocation.ok_or(TraceConversionError( diff --git a/crates/rpc/src/v06/types/transaction.rs b/crates/rpc/src/v06/types/transaction.rs index 08fedbe847..ae7b853eeb 100644 --- a/crates/rpc/src/v06/types/transaction.rs +++ b/crates/rpc/src/v06/types/transaction.rs @@ -1,8 +1,7 @@ use pathfinder_common::transaction::{ DataAvailabilityMode, DeclareTransactionV0V1, DeclareTransactionV2, DeclareTransactionV3, - DeployAccountTransactionV0V1, DeployAccountTransactionV3, DeployTransaction, - InvokeTransactionV0, InvokeTransactionV1, InvokeTransactionV3, L1HandlerTransaction, - ResourceBound, ResourceBounds, + DeployAccountTransactionV1, DeployAccountTransactionV3, DeployTransaction, InvokeTransactionV0, + InvokeTransactionV1, InvokeTransactionV3, L1HandlerTransaction, ResourceBound, ResourceBounds, }; use pathfinder_common::{ ResourceAmount, ResourcePricePerUnit, Tip, TransactionHash, TransactionVersion, @@ -43,8 +42,8 @@ impl Serialize for Transaction { TransactionVariant::DeclareV2(x) => DeclareV2Helper(x).serialize(serializer), TransactionVariant::DeclareV3(x) => DeclareV3Helper(x).serialize(serializer), TransactionVariant::Deploy(x) => DeployHelper(x).serialize(serializer), - TransactionVariant::DeployAccountV0V1(x) => { - DeployAccountV0V1Helper(x).serialize(serializer) + TransactionVariant::DeployAccountV1(x) => { + DeployAccountV1Helper(x).serialize(serializer) } TransactionVariant::DeployAccountV3(x) => { DeployAccountV3Helper(x).serialize(serializer) @@ -62,7 +61,7 @@ struct DeclareV1Helper<'a>(&'a DeclareTransactionV0V1); struct DeclareV2Helper<'a>(&'a DeclareTransactionV2); struct DeclareV3Helper<'a>(&'a DeclareTransactionV3); struct DeployHelper<'a>(&'a DeployTransaction); -struct DeployAccountV0V1Helper<'a>(&'a DeployAccountTransactionV0V1); +struct DeployAccountV1Helper<'a>(&'a DeployAccountTransactionV1); struct DeployAccountV3Helper<'a>(&'a DeployAccountTransactionV3); struct InvokeV0Helper<'a>(&'a InvokeTransactionV0); struct InvokeV1Helper<'a>(&'a InvokeTransactionV1); @@ -174,7 +173,7 @@ impl Serialize for DeployHelper<'_> { } } -impl Serialize for DeployAccountV0V1Helper<'_> { +impl Serialize for DeployAccountV1Helper<'_> { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, @@ -182,7 +181,7 @@ impl Serialize for DeployAccountV0V1Helper<'_> { let mut s = serializer.serialize_struct("DeployAccount", 8)?; s.serialize_field("type", "DEPLOY_ACCOUNT")?; s.serialize_field("max_fee", &self.0.max_fee)?; - s.serialize_field("version", &TransactionVersionHelper(&self.0.version))?; + s.serialize_field("version", "0x1")?; s.serialize_field("signature", &self.0.signature)?; s.serialize_field("nonce", &self.0.nonce)?; s.serialize_field("contract_address_salt", &self.0.contract_address_salt)?; @@ -570,10 +569,9 @@ mod tests { #[test] fn deploy_account_v1() { - let original: TransactionVariant = DeployAccountTransactionV0V1 { + let original: TransactionVariant = DeployAccountTransactionV1 { contract_address: contract_address!("0xabc"), max_fee: fee!("0x1111"), - version: TransactionVersion::ONE, signature: vec![ transaction_signature_elem!("0xa1b1"), transaction_signature_elem!("0x1a1b"), diff --git a/crates/rpc/src/v07/dto/receipt.rs b/crates/rpc/src/v07/dto/receipt.rs index 64484c67dc..b17342adbf 100644 --- a/crates/rpc/src/v07/dto/receipt.rs +++ b/crates/rpc/src/v07/dto/receipt.rs @@ -209,7 +209,7 @@ impl TxnReceipt { contract_address: tx.contract_address, common, }, - TransactionVariant::DeployAccountV0V1(tx) => Self::DeployAccount { + TransactionVariant::DeployAccountV1(tx) => Self::DeployAccount { contract_address: tx.contract_address, common, }, @@ -252,7 +252,7 @@ impl PendingTxnReceipt { contract_address: tx.contract_address, common, }, - TransactionVariant::DeployAccountV0V1(tx) => Self::DeployAccount { + TransactionVariant::DeployAccountV1(tx) => Self::DeployAccount { contract_address: tx.contract_address, common, }, diff --git a/crates/storage/src/connection/transaction.rs b/crates/storage/src/connection/transaction.rs index 37ebdafe37..5993d8fd84 100644 --- a/crates/storage/src/connection/transaction.rs +++ b/crates/storage/src/connection/transaction.rs @@ -1112,49 +1112,16 @@ pub(crate) mod dto { transaction_hash: transaction_hash.as_inner().to_owned().into(), version: version.0.into(), })), - DeployAccountV0V1(DeployAccountTransactionV0V1 { + DeployAccountV1(DeployAccountTransactionV1 { contract_address, max_fee, - version, - signature, - nonce, - contract_address_salt, - constructor_calldata, - class_hash, - }) if version == TransactionVersion::ZERO => { - Self::V0(TransactionV0::DeployAccount( - self::DeployAccountTransaction::V0(self::DeployAccountTransactionV0V1 { - contract_address: contract_address.as_inner().to_owned().into(), - transaction_hash: transaction_hash.as_inner().to_owned().into(), - max_fee: max_fee.as_inner().to_owned().into(), - signature: signature - .into_iter() - .map(|x| x.as_inner().to_owned().into()) - .collect(), - nonce: nonce.as_inner().to_owned().into(), - contract_address_salt: contract_address_salt - .as_inner() - .to_owned() - .into(), - constructor_calldata: constructor_calldata - .into_iter() - .map(|x| x.as_inner().to_owned().into()) - .collect(), - class_hash: class_hash.as_inner().to_owned().into(), - }), - )) - } - DeployAccountV0V1(DeployAccountTransactionV0V1 { - contract_address, - max_fee, - version, signature, nonce, contract_address_salt, constructor_calldata, class_hash, - }) if version == TransactionVersion::ONE => Self::V0(TransactionV0::DeployAccount( - self::DeployAccountTransaction::V1(self::DeployAccountTransactionV0V1 { + }) => Self::V0(TransactionV0::DeployAccount( + self::DeployAccountTransaction::V1(self::DeployAccountTransactionV1 { contract_address: contract_address.as_inner().to_owned().into(), transaction_hash: transaction_hash.as_inner().to_owned().into(), max_fee: max_fee.as_inner().to_owned().into(), @@ -1171,9 +1138,6 @@ pub(crate) mod dto { class_hash: class_hash.as_inner().to_owned().into(), }), )), - DeployAccountV0V1(DeployAccountTransactionV0V1 { version, .. }) => { - panic!("invalid version {version:?}") - } DeployAccountV3(DeployAccountTransactionV3 { contract_address, signature, @@ -1444,37 +1408,8 @@ pub(crate) mod dto { version: TransactionVersion(version.into()), }) } - Transaction::V0(TransactionV0::DeployAccount(DeployAccountTransaction::V0( - DeployAccountTransactionV0V1 { - contract_address, - transaction_hash: _, - max_fee, - signature, - nonce, - contract_address_salt, - constructor_calldata, - class_hash, - }, - ))) => TransactionVariant::DeployAccountV0V1( - pathfinder_common::transaction::DeployAccountTransactionV0V1 { - contract_address: ContractAddress::new_or_panic(contract_address.into()), - max_fee: Fee(max_fee.into()), - version: TransactionVersion::ZERO, - signature: signature - .into_iter() - .map(|x| TransactionSignatureElem(x.into())) - .collect(), - nonce: TransactionNonce(nonce.into()), - contract_address_salt: ContractAddressSalt(contract_address_salt.into()), - constructor_calldata: constructor_calldata - .into_iter() - .map(|x| CallParam(x.into())) - .collect(), - class_hash: ClassHash(class_hash.into()), - }, - ), Transaction::V0(TransactionV0::DeployAccount(DeployAccountTransaction::V1( - DeployAccountTransactionV0V1 { + DeployAccountTransactionV1 { contract_address, transaction_hash: _, max_fee, @@ -1484,11 +1419,10 @@ pub(crate) mod dto { constructor_calldata, class_hash, }, - ))) => TransactionVariant::DeployAccountV0V1( - pathfinder_common::transaction::DeployAccountTransactionV0V1 { + ))) => TransactionVariant::DeployAccountV1( + pathfinder_common::transaction::DeployAccountTransactionV1 { contract_address: ContractAddress::new_or_panic(contract_address.into()), max_fee: Fee(max_fee.into()), - version: TransactionVersion::ONE, signature: signature .into_iter() .map(|x| TransactionSignatureElem(x.into())) @@ -1657,9 +1591,7 @@ pub(crate) mod dto { }, Transaction::V0(TransactionV0::Deploy(t)) => t.transaction_hash.clone(), Transaction::V0(TransactionV0::DeployAccount(t)) => match t { - DeployAccountTransaction::V0(t) | DeployAccountTransaction::V1(t) => { - t.transaction_hash.clone() - } + DeployAccountTransaction::V1(t) => t.transaction_hash.clone(), DeployAccountTransaction::V3(t) => t.transaction_hash.clone(), }, Transaction::V0(TransactionV0::Invoke(t)) => match t { @@ -1801,14 +1733,13 @@ pub(crate) mod dto { /// Represents deserialized L2 deploy account transaction data. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Dummy)] pub enum DeployAccountTransaction { - V0(DeployAccountTransactionV0V1), - V1(DeployAccountTransactionV0V1), + V1(DeployAccountTransactionV1), V3(DeployAccountTransactionV3), } #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] - pub struct DeployAccountTransactionV0V1 { + pub struct DeployAccountTransactionV1 { pub contract_address: MinimalFelt, pub transaction_hash: MinimalFelt, pub max_fee: MinimalFelt, @@ -1819,7 +1750,7 @@ pub(crate) mod dto { pub class_hash: MinimalFelt, } - impl Dummy for DeployAccountTransactionV0V1 { + impl Dummy for DeployAccountTransactionV1 { fn dummy_with_rng(_: &T, rng: &mut R) -> Self { let contract_address_salt = Faker.fake_with_rng(rng); let constructor_calldata: Vec = Faker.fake_with_rng(rng); @@ -2126,10 +2057,9 @@ mod tests { }, StarknetTransaction { hash: transaction_hash_bytes!(b"deploy account tx hash"), - variant: TransactionVariant::DeployAccountV0V1(DeployAccountTransactionV0V1 { + variant: TransactionVariant::DeployAccountV1(DeployAccountTransactionV1 { contract_address: contract_address_bytes!(b"deploy account contract address"), max_fee: fee_bytes!(b"deploy account max fee"), - version: TransactionVersion::ZERO, signature: vec![ transaction_signature_elem_bytes!(b"deploy account tx sig 0"), transaction_signature_elem_bytes!(b"deploy account tx sig 1"), diff --git a/crates/storage/src/schema/revision_0051.rs b/crates/storage/src/schema/revision_0051.rs index cbb3086f14..486fa64733 100644 --- a/crates/storage/src/schema/revision_0051.rs +++ b/crates/storage/src/schema/revision_0051.rs @@ -38,15 +38,6 @@ pub(crate) fn migrate(tx: &rusqlite::Transaction<'_>) -> anyhow::Result<()> { let events = mem::take(&mut receipt.events); let receipt = pathfinder_common::receipt::Receipt::from(receipt); - if let pathfinder_common::transaction::TransactionVariant::DeployAccountV0V1( - ref t, - ) = transaction.variant - { - if t.version == pathfinder_common::TransactionVersion::ZERO { - panic!("version zero"); - } - } - // Serialize into new DTOs. let transaction = crate::transaction::dto::Transaction::from(&transaction); let transaction = @@ -797,10 +788,9 @@ pub(crate) mod old_dto { transaction_hash, version, }), - DeployAccountV0V1(DeployAccountTransactionV0V1 { + DeployAccountV1(DeployAccountTransactionV1 { contract_address, max_fee, - version, signature, nonce, contract_address_salt, @@ -811,7 +801,7 @@ pub(crate) mod old_dto { contract_address, transaction_hash, max_fee, - version, + version: TransactionVersion::ONE, signature, nonce, contract_address_salt, @@ -1031,18 +1021,24 @@ pub(crate) mod old_dto { constructor_calldata, class_hash, }, - )) => TransactionVariant::DeployAccountV0V1( - pathfinder_common::transaction::DeployAccountTransactionV0V1 { - contract_address, - max_fee, - version, - signature, - nonce, - contract_address_salt, - constructor_calldata, - class_hash, - }, - ), + )) if version.without_query_version() + == TransactionVersion::ONE.without_query_version() => + { + TransactionVariant::DeployAccountV1( + pathfinder_common::transaction::DeployAccountTransactionV1 { + contract_address, + max_fee, + signature, + nonce, + contract_address_salt, + constructor_calldata, + class_hash, + }, + ) + } + Transaction::DeployAccount(DeployAccountTransaction::V0V1( + DeployAccountTransactionV0V1 { version, .. }, + )) => panic!("unexpected version for DeployAccountV0V1: {version:?}"), Transaction::DeployAccount(DeployAccountTransaction::V3( DeployAccountTransactionV3 { nonce, From 3ef43dc9da1e5cb9af44b884a9e4ae566560f0c3 Mon Sep 17 00:00:00 2001 From: sistemd Date: Wed, 20 Mar 2024 20:24:44 +0100 Subject: [PATCH 12/24] make actual_fee non optional --- crates/common/src/receipt.rs | 2 +- crates/gateway-types/src/reply.rs | 8 +- crates/p2p/src/client/conv.rs | 2 +- crates/pathfinder/examples/re_execute.rs | 82 +++++++++---------- .../src/p2p_network/sync_handlers/conv.rs | 2 +- crates/rpc/src/lib.rs | 2 +- .../src/v04/method/get_transaction_receipt.rs | 8 +- .../src/v06/method/get_transaction_receipt.rs | 4 +- crates/rpc/src/v07/dto/receipt.rs | 4 +- crates/storage/src/connection/transaction.rs | 26 +++--- crates/storage/src/schema/revision_0051.rs | 8 +- crates/storage/src/test_utils.rs | 2 +- 12 files changed, 79 insertions(+), 71 deletions(-) diff --git a/crates/common/src/receipt.rs b/crates/common/src/receipt.rs index 3b9da95617..46e8cd3f44 100644 --- a/crates/common/src/receipt.rs +++ b/crates/common/src/receipt.rs @@ -2,7 +2,7 @@ use crate::prelude::*; #[derive(Clone, Default, Debug, PartialEq, Eq)] pub struct Receipt { - pub actual_fee: Option, + pub actual_fee: Fee, pub execution_resources: ExecutionResources, pub l2_to_l1_messages: Vec, pub execution_status: ExecutionStatus, diff --git a/crates/gateway-types/src/reply.rs b/crates/gateway-types/src/reply.rs index d9a317c190..4d1e1b8e95 100644 --- a/crates/gateway-types/src/reply.rs +++ b/crates/gateway-types/src/reply.rs @@ -630,7 +630,11 @@ pub(crate) mod transaction { }; Self { - actual_fee, + actual_fee: if actual_fee == Fee::ZERO { + None + } else { + Some(actual_fee) + }, events, execution_resources: Some(execution_resources.into()), l1_to_l2_consumed_message: None, @@ -712,7 +716,7 @@ pub(crate) mod transaction { ( common::Receipt { - actual_fee, + actual_fee: actual_fee.unwrap_or_default(), execution_resources: execution_resources.unwrap_or_default().into(), l2_to_l1_messages: l2_to_l1_messages.into_iter().map(Into::into).collect(), transaction_hash, diff --git a/crates/p2p/src/client/conv.rs b/crates/p2p/src/client/conv.rs index a02ef5f9fd..e0e59a2cc4 100644 --- a/crates/p2p/src/client/conv.rs +++ b/crates/p2p/src/client/conv.rs @@ -274,7 +274,7 @@ impl TryFromDto<(p2p_proto::receipt::Receipt, TransactionIndex)> for Receipt { | Deploy(DeployTransactionReceipt { common, .. }) | DeployAccount(DeployAccountTransactionReceipt { common, .. }) => Ok(Self { transaction_hash: TransactionHash(common.transaction_hash.0), - actual_fee: Some(Fee(common.actual_fee)), + actual_fee: Fee(common.actual_fee), execution_resources: ExecutionResources { builtins: BuiltinCounters { output: common.execution_resources.builtins.output.into(), diff --git a/crates/pathfinder/examples/re_execute.rs b/crates/pathfinder/examples/re_execute.rs index 7cd9e3012c..a3c51c86dc 100644 --- a/crates/pathfinder/examples/re_execute.rs +++ b/crates/pathfinder/examples/re_execute.rs @@ -150,48 +150,46 @@ fn execute(storage: &mut Storage, chain_id: ChainId, work: Work) { match pathfinder_executor::simulate(execution_state, transactions, false, false) { Ok(simulations) => { for (simulation, receipt) in simulations.iter().zip(work.receipts.iter()) { - if let Some(actual_fee) = receipt.actual_fee { - let actual_fee = - u128::from_be_bytes(actual_fee.0.to_be_bytes()[16..].try_into().unwrap()); - - // L1 handler transactions have a fee of zero in the receipt. - if actual_fee == 0 { - continue; - } - - let estimate = &simulation.fee_estimation; - - let (gas_price, data_gas_price) = match estimate.unit { - pathfinder_executor::types::PriceUnit::Wei => ( - work.header.eth_l1_gas_price.0, - work.header.eth_l1_data_gas_price.0, - ), - pathfinder_executor::types::PriceUnit::Fri => ( - work.header.strk_l1_gas_price.0, - work.header.strk_l1_data_gas_price.0, - ), - }; - - let actual_data_gas_consumed = - receipt.execution_resources.data_availability.l1_data_gas; - let actual_gas_consumed = (actual_fee - - actual_data_gas_consumed.saturating_mul(data_gas_price)) - / gas_price.max(1); - - let estimated_gas_consumed = estimate.gas_consumed.as_u128(); - let estimated_data_gas_consumed = estimate.data_gas_consumed.as_u128(); - - let gas_diff = actual_gas_consumed.abs_diff(estimated_gas_consumed); - let data_gas_diff = - actual_data_gas_consumed.abs_diff(estimated_data_gas_consumed); - - if gas_diff > (actual_gas_consumed * 2 / 10) - || data_gas_diff > (actual_data_gas_consumed * 2 / 10) - { - tracing::warn!(block_number=%work.header.number, transaction_hash=%receipt.transaction_hash, %estimated_gas_consumed, %actual_gas_consumed, %estimated_data_gas_consumed, %actual_data_gas_consumed, estimated_fee=%estimate.overall_fee, %actual_fee, "Estimation mismatch"); - } else { - tracing::debug!(block_number=%work.header.number, transaction_hash=%receipt.transaction_hash, %estimated_gas_consumed, %actual_gas_consumed, %estimated_data_gas_consumed, %actual_data_gas_consumed, estimated_fee=%estimate.overall_fee, %actual_fee, "Estimation matches"); - } + let actual_fee = u128::from_be_bytes( + receipt.actual_fee.0.to_be_bytes()[16..].try_into().unwrap(), + ); + + // L1 handler transactions have a fee of zero in the receipt. + if actual_fee == 0 { + continue; + } + + let estimate = &simulation.fee_estimation; + + let (gas_price, data_gas_price) = match estimate.unit { + pathfinder_executor::types::PriceUnit::Wei => ( + work.header.eth_l1_gas_price.0, + work.header.eth_l1_data_gas_price.0, + ), + pathfinder_executor::types::PriceUnit::Fri => ( + work.header.strk_l1_gas_price.0, + work.header.strk_l1_data_gas_price.0, + ), + }; + + let actual_data_gas_consumed = + receipt.execution_resources.data_availability.l1_data_gas; + let actual_gas_consumed = (actual_fee + - actual_data_gas_consumed.saturating_mul(data_gas_price)) + / gas_price.max(1); + + let estimated_gas_consumed = estimate.gas_consumed.as_u128(); + let estimated_data_gas_consumed = estimate.data_gas_consumed.as_u128(); + + let gas_diff = actual_gas_consumed.abs_diff(estimated_gas_consumed); + let data_gas_diff = actual_data_gas_consumed.abs_diff(estimated_data_gas_consumed); + + if gas_diff > (actual_gas_consumed * 2 / 10) + || data_gas_diff > (actual_data_gas_consumed * 2 / 10) + { + tracing::warn!(block_number=%work.header.number, transaction_hash=%receipt.transaction_hash, %estimated_gas_consumed, %actual_gas_consumed, %estimated_data_gas_consumed, %actual_data_gas_consumed, estimated_fee=%estimate.overall_fee, %actual_fee, "Estimation mismatch"); + } else { + tracing::debug!(block_number=%work.header.number, transaction_hash=%receipt.transaction_hash, %estimated_gas_consumed, %actual_gas_consumed, %estimated_data_gas_consumed, %actual_data_gas_consumed, estimated_fee=%estimate.overall_fee, %actual_fee, "Estimation matches"); } } } diff --git a/crates/pathfinder/src/p2p_network/sync_handlers/conv.rs b/crates/pathfinder/src/p2p_network/sync_handlers/conv.rs index 2d78bb2a1d..73900ad337 100644 --- a/crates/pathfinder/src/p2p_network/sync_handlers/conv.rs +++ b/crates/pathfinder/src/p2p_network/sync_handlers/conv.rs @@ -198,7 +198,7 @@ impl ToDto for (Transaction, Receipt) { .unwrap_or_default(); let common = ReceiptCommon { transaction_hash: Hash(self.1.transaction_hash.0), - actual_fee: self.1.actual_fee.unwrap_or_default().0, + actual_fee: self.1.actual_fee.0, messages_sent: self .1 .l2_to_l1_messages diff --git a/crates/rpc/src/lib.rs b/crates/rpc/src/lib.rs index 52ff11d51d..1ac64a1192 100644 --- a/crates/rpc/src/lib.rs +++ b/crates/rpc/src/lib.rs @@ -668,7 +668,7 @@ pub mod test_utils { let transaction_receipts = vec![ ( Receipt { - actual_fee: None, + actual_fee: Fee::ZERO, execution_resources: ExecutionResources::default(), transaction_hash: transactions[0].hash, transaction_index: TransactionIndex::new_or_panic(0), diff --git a/crates/rpc/src/v04/method/get_transaction_receipt.rs b/crates/rpc/src/v04/method/get_transaction_receipt.rs index eac7f99fb1..01a5c3e388 100644 --- a/crates/rpc/src/v04/method/get_transaction_receipt.rs +++ b/crates/rpc/src/v04/method/get_transaction_receipt.rs @@ -214,9 +214,7 @@ pub mod types { let revert_reason = receipt.revert_reason().map(ToOwned::to_owned); let common = CommonTransactionReceiptProperties { transaction_hash: receipt.transaction_hash, - actual_fee: receipt - .actual_fee - .unwrap_or_else(|| Fee(Default::default())), + actual_fee: receipt.actual_fee, block_hash, block_number, messages_sent: receipt @@ -347,9 +345,7 @@ pub mod types { let revert_reason = receipt.revert_reason().map(ToOwned::to_owned); let common = CommonPendingTransactionReceiptProperties { transaction_hash: receipt.transaction_hash, - actual_fee: receipt - .actual_fee - .unwrap_or_else(|| Fee(Default::default())), + actual_fee: receipt.actual_fee, messages_sent: receipt .l2_to_l1_messages .into_iter() diff --git a/crates/rpc/src/v06/method/get_transaction_receipt.rs b/crates/rpc/src/v06/method/get_transaction_receipt.rs index 48a156273b..8f7780070a 100644 --- a/crates/rpc/src/v06/method/get_transaction_receipt.rs +++ b/crates/rpc/src/v06/method/get_transaction_receipt.rs @@ -459,7 +459,7 @@ pub mod types { block_number: BlockNumber, transaction: pathfinder_common::transaction::Transaction, ) -> Self { - let fee_amount = receipt.actual_fee.unwrap_or_default(); + let fee_amount = receipt.actual_fee; let fee_unit = match transaction.version() { TransactionVersion::ZERO | TransactionVersion::ONE | TransactionVersion::TWO => { PriceUnit::Wei @@ -606,7 +606,7 @@ pub mod types { events: Vec, transaction: &pathfinder_common::transaction::Transaction, ) -> Self { - let fee_amount = receipt.actual_fee.unwrap_or_default(); + let fee_amount = receipt.actual_fee; let fee_unit = PriceUnit::for_transaction_version(&transaction.version()); let actual_fee = FeePayment::V06 { diff --git a/crates/rpc/src/v07/dto/receipt.rs b/crates/rpc/src/v07/dto/receipt.rs index b17342adbf..26c8799fd0 100644 --- a/crates/rpc/src/v07/dto/receipt.rs +++ b/crates/rpc/src/v07/dto/receipt.rs @@ -361,7 +361,7 @@ impl CommonReceiptProperties { finality_status: v06::FinalityStatus, ) -> Self { let actual_fee = FeePayment { - amount: receipt.actual_fee.unwrap_or_default(), + amount: receipt.actual_fee, unit: transaction.version().into(), }; @@ -398,7 +398,7 @@ impl PendingCommonReceiptProperties { finality_status: v06::FinalityStatus, ) -> Self { let actual_fee = FeePayment { - amount: receipt.actual_fee.unwrap_or_default(), + amount: receipt.actual_fee, unit: transaction.version().into(), }; diff --git a/crates/storage/src/connection/transaction.rs b/crates/storage/src/connection/transaction.rs index 5993d8fd84..de70341400 100644 --- a/crates/storage/src/connection/transaction.rs +++ b/crates/storage/src/connection/transaction.rs @@ -810,11 +810,11 @@ pub(crate) mod dto { } /// Represents deserialized L2 transaction receipt data. - #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Dummy)] + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct ReceiptV0 { #[serde(default)] - pub actual_fee: Option, + pub actual_fee: MinimalFelt, pub execution_resources: Option, pub l2_to_l1_messages: Vec, pub transaction_hash: MinimalFelt, @@ -844,7 +844,7 @@ pub(crate) mod dto { }) = value; common::Receipt { - actual_fee: actual_fee.map(|x| Fee(x.into())), + actual_fee: Fee(actual_fee.into()), execution_resources: (&execution_resources.unwrap_or_default()).into(), l2_to_l1_messages: l2_to_l1_messages.into_iter().map(Into::into).collect(), transaction_hash: TransactionHash(transaction_hash.into()), @@ -869,7 +869,7 @@ pub(crate) mod dto { }; Self::V0(ReceiptV0 { - actual_fee: value.actual_fee.map(|x| x.as_inner().to_owned().into()), + actual_fee: value.actual_fee.as_inner().to_owned().into(), execution_resources: Some((&value.execution_resources).into()), l2_to_l1_messages: value.l2_to_l1_messages.iter().map(Into::into).collect(), transaction_hash: value.transaction_hash.as_inner().to_owned().into(), @@ -880,22 +880,28 @@ pub(crate) mod dto { } } - impl Dummy for Receipt { + impl Dummy for ReceiptV0 { fn dummy_with_rng(_: &T, rng: &mut R) -> Self { let execution_status = Faker.fake_with_rng(rng); - let revert_error = - (execution_status == ExecutionStatus::Reverted).then(|| Faker.fake_with_rng(rng)); + let revert_error = (execution_status == ExecutionStatus::Reverted).then(|| { + let error: String = Faker.fake_with_rng(rng); + if error.is_empty() { + "Revert error".to_string() + } else { + error + } + }); // Those fields that were missing in very old receipts are always present - Self::V0(ReceiptV0 { - actual_fee: Some(Faker.fake_with_rng(rng)), + ReceiptV0 { + actual_fee: Faker.fake_with_rng(rng), execution_resources: Some(Faker.fake_with_rng(rng)), l2_to_l1_messages: Faker.fake_with_rng(rng), transaction_hash: Faker.fake_with_rng(rng), transaction_index: Faker.fake_with_rng(rng), execution_status, revert_error, - }) + } } } diff --git a/crates/storage/src/schema/revision_0051.rs b/crates/storage/src/schema/revision_0051.rs index 486fa64733..0cd946a22a 100644 --- a/crates/storage/src/schema/revision_0051.rs +++ b/crates/storage/src/schema/revision_0051.rs @@ -447,7 +447,11 @@ pub(crate) mod old_dto { }; Self { - actual_fee: value.actual_fee, + actual_fee: if value.actual_fee == Fee::ZERO { + None + } else { + Some(value.actual_fee) + }, events: vec![], execution_resources: Some((&value.execution_resources).into()), // We don't care about this field anymore. @@ -479,7 +483,7 @@ pub(crate) mod old_dto { } = value; common::Receipt { - actual_fee, + actual_fee: actual_fee.unwrap_or_default(), execution_resources: (&execution_resources.unwrap_or_default()).into(), l2_to_l1_messages: l2_to_l1_messages.into_iter().map(Into::into).collect(), transaction_hash, diff --git a/crates/storage/src/test_utils.rs b/crates/storage/src/test_utils.rs index 9d34517683..30100a55ae 100644 --- a/crates/storage/src/test_utils.rs +++ b/crates/storage/src/test_utils.rs @@ -110,7 +110,7 @@ pub(crate) fn create_transactions_and_receipts( let tx_receipt = transactions.enumerate().map(|(i, tx)| { let receipt = Receipt { - actual_fee: None, + actual_fee: Fee::ZERO, execution_resources: ExecutionResources { builtins: Default::default(), n_steps: i as u64 + 987, From 9f015f79e6cf48552d589e71f359d90ccf36632c Mon Sep 17 00:00:00 2001 From: sistemd Date: Thu, 21 Mar 2024 11:54:42 +0100 Subject: [PATCH 13/24] panic migration if actual_fee is missing --- crates/storage/src/schema/revision_0051.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/storage/src/schema/revision_0051.rs b/crates/storage/src/schema/revision_0051.rs index 0cd946a22a..4b79a23e57 100644 --- a/crates/storage/src/schema/revision_0051.rs +++ b/crates/storage/src/schema/revision_0051.rs @@ -36,6 +36,9 @@ pub(crate) fn migrate(tx: &rusqlite::Transaction<'_>) -> anyhow::Result<()> { .context("Deserializing receipt") .unwrap(); let events = mem::take(&mut receipt.events); + if receipt.actual_fee.is_none() { + panic!("receipt with no fee {receipt:?}"); + } let receipt = pathfinder_common::receipt::Receipt::from(receipt); // Serialize into new DTOs. From 0ecabcbc52d9d4728b3181c093e3f0160d78c5da Mon Sep 17 00:00:00 2001 From: sistemd Date: Thu, 21 Mar 2024 11:59:55 +0100 Subject: [PATCH 14/24] handle comment --- crates/storage/src/connection/transaction.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/storage/src/connection/transaction.rs b/crates/storage/src/connection/transaction.rs index de70341400..5ae41a23ca 100644 --- a/crates/storage/src/connection/transaction.rs +++ b/crates/storage/src/connection/transaction.rs @@ -517,7 +517,6 @@ pub(crate) mod dto { fn from(value: MinimalFelt) -> Self { let mut bytes = [0; 32]; let num_zeros = bytes.len() - value.0.len(); - bytes[..num_zeros].fill(0); bytes[num_zeros..].copy_from_slice(&value.0); Felt::from_be_bytes(bytes).unwrap() } From c53ba78b5ef533b7343783828e530ecdf87e65c8 Mon Sep 17 00:00:00 2001 From: sistemd Date: Thu, 21 Mar 2024 12:05:13 +0100 Subject: [PATCH 15/24] fix doc --- crates/p2p/src/client/conv.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/p2p/src/client/conv.rs b/crates/p2p/src/client/conv.rs index e0e59a2cc4..77afe44d71 100644 --- a/crates/p2p/src/client/conv.rs +++ b/crates/p2p/src/client/conv.rs @@ -93,7 +93,7 @@ impl TryFromDto for TransactionVaria /// ## Important /// /// This conversion does not compute deployed contract address for deploy account transactions - /// ([`TransactionVariant::DeployAccountV0V1`] and [`TransactionVariant::DeployAccountV3`]), + /// ([`TransactionVariant::DeployAccountV1`] and [`TransactionVariant::DeployAccountV3`]), /// filling it with a zero address instead. The caller is responsible for performing the computation after the conversion succeeds. fn try_from_dto(dto: p2p_proto::transaction::TransactionVariant) -> anyhow::Result where From 20b9820a259dd31d94f3d0b88bcb99a3779fa06e Mon Sep 17 00:00:00 2001 From: sistemd Date: Thu, 21 Mar 2024 12:21:06 +0100 Subject: [PATCH 16/24] don't set rpc actual_fee to none --- crates/gateway-types/src/reply.rs | 6 +----- crates/storage/src/schema/revision_0051.rs | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/crates/gateway-types/src/reply.rs b/crates/gateway-types/src/reply.rs index 4d1e1b8e95..0855e6986c 100644 --- a/crates/gateway-types/src/reply.rs +++ b/crates/gateway-types/src/reply.rs @@ -630,11 +630,7 @@ pub(crate) mod transaction { }; Self { - actual_fee: if actual_fee == Fee::ZERO { - None - } else { - Some(actual_fee) - }, + actual_fee: Some(actual_fee), events, execution_resources: Some(execution_resources.into()), l1_to_l2_consumed_message: None, diff --git a/crates/storage/src/schema/revision_0051.rs b/crates/storage/src/schema/revision_0051.rs index 4b79a23e57..4aa086fcc6 100644 --- a/crates/storage/src/schema/revision_0051.rs +++ b/crates/storage/src/schema/revision_0051.rs @@ -450,11 +450,7 @@ pub(crate) mod old_dto { }; Self { - actual_fee: if value.actual_fee == Fee::ZERO { - None - } else { - Some(value.actual_fee) - }, + actual_fee: Some(value.actual_fee), events: vec![], execution_resources: Some((&value.execution_resources).into()), // We don't care about this field anymore. From 2e05c43ad05389e8b2f97e6d637e255a4fd61008 Mon Sep 17 00:00:00 2001 From: sistemd Date: Thu, 21 Mar 2024 12:42:51 +0100 Subject: [PATCH 17/24] don't panic when actual_fee is missing --- crates/storage/src/schema/revision_0051.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/storage/src/schema/revision_0051.rs b/crates/storage/src/schema/revision_0051.rs index 4aa086fcc6..2db3dda689 100644 --- a/crates/storage/src/schema/revision_0051.rs +++ b/crates/storage/src/schema/revision_0051.rs @@ -36,9 +36,6 @@ pub(crate) fn migrate(tx: &rusqlite::Transaction<'_>) -> anyhow::Result<()> { .context("Deserializing receipt") .unwrap(); let events = mem::take(&mut receipt.events); - if receipt.actual_fee.is_none() { - panic!("receipt with no fee {receipt:?}"); - } let receipt = pathfinder_common::receipt::Receipt::from(receipt); // Serialize into new DTOs. From a38094a2ef219642500606965a5555caf3c8eb8e Mon Sep 17 00:00:00 2001 From: sistemd Date: Fri, 22 Mar 2024 11:33:52 +0100 Subject: [PATCH 18/24] minimize flume deps --- Cargo.lock | 2 -- crates/storage/Cargo.toml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9e0e179cf7..044294a8e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3600,8 +3600,6 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" dependencies = [ - "futures-core", - "futures-sink", "nanorand", "spin 0.9.8", ] diff --git a/crates/storage/Cargo.toml b/crates/storage/Cargo.toml index fb2fa9b727..2a7ca5ea17 100644 --- a/crates/storage/Cargo.toml +++ b/crates/storage/Cargo.toml @@ -17,7 +17,7 @@ cached = { workspace = true } const_format = { workspace = true } data-encoding = "2.4.0" fake = { workspace = true } -flume = "0.11.0" +flume = { version = "0.11.0", default-features = false, features = ["eventual-fairness"] } hex = { workspace = true } lazy_static = { workspace = true } pathfinder-common = { path = "../common" } From c6a6019c6ca298bac0eae015e7330319264ce7b6 Mon Sep 17 00:00:00 2001 From: sistemd Date: Fri, 22 Mar 2024 11:39:20 +0100 Subject: [PATCH 19/24] fallback for available_parallelism --- crates/storage/src/schema/revision_0051.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/storage/src/schema/revision_0051.rs b/crates/storage/src/schema/revision_0051.rs index 2db3dda689..c52de6e262 100644 --- a/crates/storage/src/schema/revision_0051.rs +++ b/crates/storage/src/schema/revision_0051.rs @@ -15,7 +15,10 @@ pub(crate) fn migrate(tx: &rusqlite::Transaction<'_>) -> anyhow::Result<()> { let (insert_tx, insert_rx) = mpsc::channel(); let (transform_tx, transform_rx) = flume::unbounded::<(Vec, i64, Vec, Vec, Vec)>(); - for _ in 0..thread::available_parallelism().unwrap().get() { + for _ in 0..thread::available_parallelism() + .map(|p| p.get()) + .unwrap_or(4) + { let insert_tx = insert_tx.clone(); let transform_rx = transform_rx.clone(); let mut compressor = zstd::bulk::Compressor::new(10).context("Create zstd compressor")?; From a64279feef4b78cd499a0bf66e5c64f7c3ef3303 Mon Sep 17 00:00:00 2001 From: sistemd Date: Fri, 22 Mar 2024 11:42:25 +0100 Subject: [PATCH 20/24] call elapsed less often --- crates/storage/src/schema/revision_0051.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/crates/storage/src/schema/revision_0051.rs b/crates/storage/src/schema/revision_0051.rs index c52de6e262..aae336c772 100644 --- a/crates/storage/src/schema/revision_0051.rs +++ b/crates/storage/src/schema/revision_0051.rs @@ -112,14 +112,6 @@ pub(crate) fn migrate(tx: &rusqlite::Transaction<'_>) -> anyhow::Result<()> { let mut rows = query_stmt.query([])?; let mut progress = 0; loop { - if progress_logged.elapsed() > LOG_RATE { - progress_logged = Instant::now(); - tracing::info!( - "Migrating rows: {:.2}%", - (progress as f64 / count as f64) * 100.0 - ); - } - let mut batch_size = 0; for _ in 0..BATCH_SIZE { match rows.next() { @@ -145,6 +137,13 @@ pub(crate) fn migrate(tx: &rusqlite::Transaction<'_>) -> anyhow::Result<()> { } } for _ in 0..batch_size { + if progress % 1000 == 0 && progress_logged.elapsed() > LOG_RATE { + progress_logged = Instant::now(); + tracing::info!( + "Migrating rows: {:.2}%", + (progress as f64 / count as f64) * 100.0 + ); + } let (hash, idx, block_hash, transaction, receipt, events) = insert_rx.recv()?; insert_stmt.execute(params![hash, idx, block_hash, transaction, receipt, events])?; progress += 1; From 280b64fe4276768421feeb549e31adcc68e20d5f Mon Sep 17 00:00:00 2001 From: sistemd Date: Fri, 22 Mar 2024 12:01:20 +0100 Subject: [PATCH 21/24] remove unneeded without_query_version calls --- crates/gateway-types/src/reply.rs | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/crates/gateway-types/src/reply.rs b/crates/gateway-types/src/reply.rs index 0855e6986c..52ef95e79f 100644 --- a/crates/gateway-types/src/reply.rs +++ b/crates/gateway-types/src/reply.rs @@ -1281,21 +1281,17 @@ pub(crate) mod transaction { constructor_calldata, class_hash, }, - )) if version.without_query_version() - == TransactionVersion::ONE.without_query_version() => - { - TransactionVariant::DeployAccountV1( - pathfinder_common::transaction::DeployAccountTransactionV1 { - contract_address, - max_fee, - signature, - nonce, - contract_address_salt, - constructor_calldata, - class_hash, - }, - ) - } + )) if version == TransactionVersion::ONE => TransactionVariant::DeployAccountV1( + pathfinder_common::transaction::DeployAccountTransactionV1 { + contract_address, + max_fee, + signature, + nonce, + contract_address_salt, + constructor_calldata, + class_hash, + }, + ), Transaction::DeployAccount(DeployAccountTransaction::V0V1( DeployAccountTransactionV0V1 { version, .. }, )) => panic!("unexpected deploy account transaction version {version:?}"), From f393213b39f72bcb5540af20d6cfefbb15619f24 Mon Sep 17 00:00:00 2001 From: sistemd Date: Fri, 22 Mar 2024 12:59:39 +0100 Subject: [PATCH 22/24] manual serde impls for MinimalFelt --- Cargo.lock | 4 - crates/p2p/src/client/peer_agnostic.rs | 7 +- crates/pathfinder/src/sync/p2p.rs | 3 +- crates/storage/Cargo.toml | 1 - crates/storage/src/connection.rs | 3 +- crates/storage/src/connection/state_update.rs | 5 +- crates/storage/src/connection/transaction.rs | 84 ++++++++++++++----- 7 files changed, 71 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 044294a8e6..b6836e362a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6406,7 +6406,6 @@ dependencies = [ "serde_json", "serde_with", "sha3", - "smallvec", "starknet-gateway-types", "tempfile", "test-log", @@ -7826,9 +7825,6 @@ name = "smallvec" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" -dependencies = [ - "serde", -] [[package]] name = "smol" diff --git a/crates/p2p/src/client/peer_agnostic.rs b/crates/p2p/src/client/peer_agnostic.rs index 79b8892bf8..e8bd39d30c 100644 --- a/crates/p2p/src/client/peer_agnostic.rs +++ b/crates/p2p/src/client/peer_agnostic.rs @@ -26,7 +26,6 @@ use pathfinder_common::{ BlockNumber, ClassHash, ContractAddress, ContractNonce, SignedBlockHeader, StorageAddress, StorageValue, }; -use smallvec::SmallVec; use tokio::{sync::RwLock, task::spawn_blocking}; use crate::client::{conv::TryFromDto, peer_aware}; @@ -213,7 +212,7 @@ impl Client { mut start: BlockNumber, stop_inclusive: BlockNumber, getter: Arc< - impl Fn(BlockNumber, NonZeroUsize) -> anyhow::Result> + impl Fn(BlockNumber, NonZeroUsize) -> anyhow::Result> + Send + Sync + 'static, @@ -349,9 +348,9 @@ impl Client { &self, start: BlockNumber, stop_inclusive: BlockNumber, - counts: &mut SmallVec<[StateUpdateCounts; 10]>, + counts: &mut Vec, getter: Arc< - impl Fn(BlockNumber, NonZeroUsize) -> anyhow::Result> + impl Fn(BlockNumber, NonZeroUsize) -> anyhow::Result> + Send + Sync + 'static, diff --git a/crates/pathfinder/src/sync/p2p.rs b/crates/pathfinder/src/sync/p2p.rs index ff53ddd224..c09f77a181 100644 --- a/crates/pathfinder/src/sync/p2p.rs +++ b/crates/pathfinder/src/sync/p2p.rs @@ -24,7 +24,6 @@ use pathfinder_common::{ use pathfinder_ethereum::EthereumStateUpdate; use pathfinder_storage::Storage; use primitive_types::H160; -use smallvec::SmallVec; use tokio::task::spawn_blocking; use crate::state::block_hash::{ @@ -406,7 +405,7 @@ impl Sync { let storage = self.storage.clone(); let getter = move |start: BlockNumber, limit: NonZeroUsize| - -> anyhow::Result> { + -> anyhow::Result> { let mut db = storage .connection() .context("Creating database connection")?; diff --git a/crates/storage/Cargo.toml b/crates/storage/Cargo.toml index 2a7ca5ea17..7a1bf188b7 100644 --- a/crates/storage/Cargo.toml +++ b/crates/storage/Cargo.toml @@ -36,7 +36,6 @@ serde_json = { workspace = true, features = [ ] } serde_with = { workspace = true } sha3 = { workspace = true } -smallvec = { workspace = true, features = ["serde"] } starknet-gateway-types = { path = "../gateway-types" } thiserror = { workspace = true } tokio = { workspace = true } diff --git a/crates/storage/src/connection.rs b/crates/storage/src/connection.rs index 89f6d8fb49..1e16eb5c69 100644 --- a/crates/storage/src/connection.rs +++ b/crates/storage/src/connection.rs @@ -25,7 +25,6 @@ pub use event::{EmittedEvent, EventFilter, EventFilterError, PageOfEvents}; pub(crate) use reorg_counter::ReorgCounter; -use smallvec::SmallVec; pub use transaction::TransactionStatus; pub use trie::{Child, Node, StoredNode}; @@ -573,7 +572,7 @@ impl<'inner> Transaction<'inner> { &self, block: BlockId, max_len: NonZeroUsize, - ) -> anyhow::Result> { + ) -> anyhow::Result> { state_update::state_update_counts(self, block, max_len) } diff --git a/crates/storage/src/connection/state_update.rs b/crates/storage/src/connection/state_update.rs index 428b5b2956..71d69bec8d 100644 --- a/crates/storage/src/connection/state_update.rs +++ b/crates/storage/src/connection/state_update.rs @@ -6,7 +6,6 @@ use pathfinder_common::{ BlockHash, BlockNumber, ClassHash, ContractAddress, ContractNonce, SierraHash, StateCommitment, StateUpdate, StorageAddress, StorageCommitment, StorageValue, }; -use smallvec::SmallVec; use crate::{prelude::*, BlockId}; @@ -323,7 +322,7 @@ pub(super) fn state_update_counts( tx: &Transaction<'_>, block: BlockId, max_len: NonZeroUsize, -) -> anyhow::Result> { +) -> anyhow::Result> { let Some((block_number, _)) = block_id(tx, block).context("Querying block header")? else { return Ok(Default::default()); }; @@ -348,7 +347,7 @@ pub(super) fn state_update_counts( }) .context("Querying state update counts")?; - let mut ret = SmallVec::<[StateUpdateCounts; 10]>::new(); + let mut ret = Vec::new(); while let Some(stat) = counts .next() diff --git a/crates/storage/src/connection/transaction.rs b/crates/storage/src/connection/transaction.rs index 5ae41a23ca..ae98aae1d4 100644 --- a/crates/storage/src/connection/transaction.rs +++ b/crates/storage/src/connection/transaction.rs @@ -483,42 +483,86 @@ pub(super) fn transaction_block_hash( } pub(crate) mod dto { + use std::fmt; + use fake::{Dummy, Fake, Faker}; use pathfinder_common::*; use pathfinder_crypto::Felt; - use serde::{Deserialize, Serialize}; - use smallvec::SmallVec; + use serde::{ser::SerializeSeq, Deserialize, Serialize}; /// Minimally encoded Felt value. - #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, Default)] - #[serde(deny_unknown_fields)] - pub struct MinimalFelt(SmallVec<[u8; 32]>); + #[derive(Clone, Debug, PartialEq, Eq, Default)] + pub struct MinimalFelt(Felt); + + impl serde::Serialize for MinimalFelt { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let len = self + .0 + .to_be_bytes() + .into_iter() + .skip_while(|&x| x == 0) + .count(); + let mut seq = serializer.serialize_seq(Some(len))?; + for elem in self.0.to_be_bytes().into_iter().skip_while(|&x| x == 0) { + seq.serialize_element(&elem)?; + } + seq.end() + } + } - impl Dummy for MinimalFelt { - fn dummy_with_rng(config: &T, rng: &mut R) -> Self { - let felt: Felt = Dummy::dummy_with_rng(config, rng); - felt.into() + impl<'de> serde::Deserialize<'de> for MinimalFelt { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct Visitor; + + impl<'de> serde::de::Visitor<'de> for Visitor { + type Value = MinimalFelt; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("a sequence") + } + + fn visit_seq(self, mut seq: B) -> Result + where + B: serde::de::SeqAccess<'de>, + { + let len = seq.size_hint().unwrap(); + let mut bytes = [0; 32]; + let num_zeros = bytes.len() - len; + let mut i = num_zeros; + while let Some(value) = seq.next_element()? { + bytes[i] = value; + i += 1; + } + Ok(MinimalFelt(Felt::from_be_bytes(bytes).unwrap())) + } + } + + deserializer.deserialize_seq(Visitor) } } impl From for MinimalFelt { fn from(value: Felt) -> Self { - Self( - value - .to_be_bytes() - .into_iter() - .skip_while(|&x| x == 0) - .collect(), - ) + Self(value) } } impl From for Felt { fn from(value: MinimalFelt) -> Self { - let mut bytes = [0; 32]; - let num_zeros = bytes.len() - value.0.len(); - bytes[num_zeros..].copy_from_slice(&value.0); - Felt::from_be_bytes(bytes).unwrap() + value.0 + } + } + + impl Dummy for MinimalFelt { + fn dummy_with_rng(config: &T, rng: &mut R) -> Self { + let felt: Felt = Dummy::dummy_with_rng(config, rng); + felt.into() } } From 311a53c39ca99f22e10c18e430f9132064a519a8 Mon Sep 17 00:00:00 2001 From: sistemd Date: Fri, 22 Mar 2024 15:56:06 +0100 Subject: [PATCH 23/24] handle remaining comments --- crates/rpc/src/executor.rs | 4 +- crates/rpc/src/v02/types.rs | 48 +++++++--------- .../method/add_deploy_account_transaction.rs | 46 ++++++++------- .../method/add_deploy_account_transaction.rs | 56 +++++++++---------- crates/storage/src/connection/transaction.rs | 16 ++---- 5 files changed, 74 insertions(+), 96 deletions(-) diff --git a/crates/rpc/src/executor.rs b/crates/rpc/src/executor.rs index b8e3f77dbf..85487bc988 100644 --- a/crates/rpc/src/executor.rs +++ b/crates/rpc/src/executor.rs @@ -91,7 +91,7 @@ pub(crate) fn map_broadcasted_transaction( }; let deployed_address = match &transaction { - BroadcastedTransaction::DeployAccount(BroadcastedDeployAccountTransaction::V0V1(tx)) => { + BroadcastedTransaction::DeployAccount(BroadcastedDeployAccountTransaction::V1(tx)) => { Some(starknet_api::core::ContractAddress( PatriciaKey::try_from(tx.deployed_contract_address().0.into_starkfelt()) .expect("No sender address overflow expected"), @@ -128,7 +128,7 @@ pub(crate) fn map_broadcasted_transaction( BroadcastedTransaction::Invoke(BroadcastedInvokeTransaction::V3(tx)) => { tx.version.has_query_version() } - BroadcastedTransaction::DeployAccount(BroadcastedDeployAccountTransaction::V0V1(tx)) => { + BroadcastedTransaction::DeployAccount(BroadcastedDeployAccountTransaction::V1(tx)) => { tx.version.has_query_version() } BroadcastedTransaction::DeployAccount(BroadcastedDeployAccountTransaction::V3(tx)) => { diff --git a/crates/rpc/src/v02/types.rs b/crates/rpc/src/v02/types.rs index 89d50b3dce..0f2382edf4 100644 --- a/crates/rpc/src/v02/types.rs +++ b/crates/rpc/src/v02/types.rs @@ -127,7 +127,7 @@ pub mod request { BroadcastedInvokeTransaction::V3(tx) => tx.version, }, BroadcastedTransaction::DeployAccount(deploy_account) => match deploy_account { - BroadcastedDeployAccountTransaction::V0V1(tx) => tx.version, + BroadcastedDeployAccountTransaction::V1(tx) => tx.version, BroadcastedDeployAccountTransaction::V3(tx) => tx.version, }, } @@ -259,14 +259,14 @@ pub mod request { serde(untagged) )] pub enum BroadcastedDeployAccountTransaction { - V0V1(BroadcastedDeployAccountTransactionV0V1), + V1(BroadcastedDeployAccountTransactionV1), V3(BroadcastedDeployAccountTransactionV3), } impl BroadcastedDeployAccountTransaction { pub fn deployed_contract_address(&self) -> ContractAddress { match self { - Self::V0V1(tx) => tx.deployed_contract_address(), + Self::V1(tx) => tx.deployed_contract_address(), Self::V3(tx) => tx.deployed_contract_address(), } } @@ -288,15 +288,15 @@ pub mod request { let v = serde_json::Value::deserialize(deserializer)?; let version = Version::deserialize(&v).map_err(de::Error::custom)?; match version.version.without_query_version() { - 0 | 1 => Ok(Self::V0V1( - BroadcastedDeployAccountTransactionV0V1::deserialize(&v) + 1 => Ok(Self::V1( + BroadcastedDeployAccountTransactionV1::deserialize(&v) .map_err(de::Error::custom)?, )), 3 => Ok(Self::V3( BroadcastedDeployAccountTransactionV3::deserialize(&v) .map_err(de::Error::custom)?, )), - _v => Err(de::Error::custom("version must be 0 or 1")), + v => Err(de::Error::custom(format!("invalid version {v}"))), } } } @@ -305,7 +305,7 @@ pub mod request { #[derive(Clone, Debug, Deserialize, PartialEq, Eq)] #[cfg_attr(any(test, feature = "rpc-full-serde"), derive(serde::Serialize))] #[serde(deny_unknown_fields)] - pub struct BroadcastedDeployAccountTransactionV0V1 { + pub struct BroadcastedDeployAccountTransactionV1 { // Fields from BROADCASTED_TXN_COMMON_PROPERTIES pub version: TransactionVersion, pub max_fee: Fee, @@ -318,7 +318,7 @@ pub mod request { pub class_hash: ClassHash, } - impl BroadcastedDeployAccountTransactionV0V1 { + impl BroadcastedDeployAccountTransactionV1 { pub fn deployed_contract_address(&self) -> ContractAddress { ContractAddress::deployed_contract_address( self.constructor_calldata.iter().copied(), @@ -526,27 +526,17 @@ pub mod request { account_deployment_data: declare.account_deployment_data, }) } - BroadcastedTransaction::DeployAccount( - BroadcastedDeployAccountTransaction::V0V1(deploy), - ) if deploy.version.without_query_version() - == TransactionVersion::ONE.without_query_version() => - { - TransactionVariant::DeployAccountV1(DeployAccountTransactionV1 { - contract_address: deploy.deployed_contract_address(), - max_fee: deploy.max_fee, - signature: deploy.signature, - nonce: deploy.nonce, - contract_address_salt: deploy.contract_address_salt, - constructor_calldata: deploy.constructor_calldata, - class_hash: deploy.class_hash, - }) - } - BroadcastedTransaction::DeployAccount( - BroadcastedDeployAccountTransaction::V0V1(deploy), - ) => panic!( - "deploy account transaction version {:?} is not supported", - deploy.version - ), + BroadcastedTransaction::DeployAccount(BroadcastedDeployAccountTransaction::V1( + deploy, + )) => TransactionVariant::DeployAccountV1(DeployAccountTransactionV1 { + contract_address: deploy.deployed_contract_address(), + max_fee: deploy.max_fee, + signature: deploy.signature, + nonce: deploy.nonce, + contract_address_salt: deploy.contract_address_salt, + constructor_calldata: deploy.constructor_calldata, + class_hash: deploy.class_hash, + }), BroadcastedTransaction::DeployAccount(BroadcastedDeployAccountTransaction::V3( deploy, )) => TransactionVariant::DeployAccountV3(DeployAccountTransactionV3 { diff --git a/crates/rpc/src/v04/method/add_deploy_account_transaction.rs b/crates/rpc/src/v04/method/add_deploy_account_transaction.rs index 7e3fbe9073..a21141f2e0 100644 --- a/crates/rpc/src/v04/method/add_deploy_account_transaction.rs +++ b/crates/rpc/src/v04/method/add_deploy_account_transaction.rs @@ -113,7 +113,7 @@ pub async fn add_deploy_account_transaction( #[cfg(test)] mod tests { use crate::v02::types::request::{ - BroadcastedDeployAccountTransactionV0V1, BroadcastedDeployAccountTransactionV3, + BroadcastedDeployAccountTransactionV1, BroadcastedDeployAccountTransactionV3, }; use crate::v02::types::{DataAvailabilityMode, ResourceBound, ResourceBounds}; @@ -184,31 +184,29 @@ mod tests { fn get_input() -> AddDeployAccountTransactionInput { AddDeployAccountTransactionInput { deploy_account_transaction: Transaction::DeployAccount( - BroadcastedDeployAccountTransaction::V0V1( - BroadcastedDeployAccountTransactionV0V1 { - version: TransactionVersion::ONE, - max_fee: fee!("0xbf391377813"), - signature: vec![ - transaction_signature_elem!( - "07dd3a55d94a0de6f3d6c104d7e6c88ec719a82f4e2bbc12587c8c187584d3d5" - ), - transaction_signature_elem!( - "071456dded17015d1234779889d78f3e7c763ddcfd2662b19e7843c7542614f8" - ), - ], - nonce: TransactionNonce::ZERO, - - contract_address_salt: contract_address_salt!( - "06d44a6aecb4339e23a9619355f101cf3cb9baec289fcd9fd51486655c1bb8a8" + BroadcastedDeployAccountTransaction::V1(BroadcastedDeployAccountTransactionV1 { + version: TransactionVersion::ONE, + max_fee: fee!("0xbf391377813"), + signature: vec![ + transaction_signature_elem!( + "07dd3a55d94a0de6f3d6c104d7e6c88ec719a82f4e2bbc12587c8c187584d3d5" ), - constructor_calldata: vec![call_param!( - "0677bb1cdc050e8d63855e8743ab6e09179138def390676cc03c484daf112ba1" - )], - class_hash: class_hash!( - "01fac3074c9d5282f0acc5c69a4781a1c711efea5e73c550c5d9fb253cf7fd3d" + transaction_signature_elem!( + "071456dded17015d1234779889d78f3e7c763ddcfd2662b19e7843c7542614f8" ), - }, - ), + ], + nonce: TransactionNonce::ZERO, + + contract_address_salt: contract_address_salt!( + "06d44a6aecb4339e23a9619355f101cf3cb9baec289fcd9fd51486655c1bb8a8" + ), + constructor_calldata: vec![call_param!( + "0677bb1cdc050e8d63855e8743ab6e09179138def390676cc03c484daf112ba1" + )], + class_hash: class_hash!( + "01fac3074c9d5282f0acc5c69a4781a1c711efea5e73c550c5d9fb253cf7fd3d" + ), + }), ), } } diff --git a/crates/rpc/src/v06/method/add_deploy_account_transaction.rs b/crates/rpc/src/v06/method/add_deploy_account_transaction.rs index 0a3b2edd4c..e75b44eb2d 100644 --- a/crates/rpc/src/v06/method/add_deploy_account_transaction.rs +++ b/crates/rpc/src/v06/method/add_deploy_account_transaction.rs @@ -1,7 +1,7 @@ use crate::context::RpcContext; use crate::felt::{RpcFelt, RpcFelt251}; use crate::v02::types::request::{ - BroadcastedDeployAccountTransaction, BroadcastedDeployAccountTransactionV0V1, + BroadcastedDeployAccountTransaction, BroadcastedDeployAccountTransactionV1, }; use pathfinder_common::{ContractAddress, TransactionHash}; use starknet_gateway_client::GatewayApi; @@ -119,8 +119,8 @@ pub(crate) async fn add_deploy_account_transaction_impl( use starknet_gateway_types::request::add_transaction; match tx { - BroadcastedDeployAccountTransaction::V0V1( - tx @ BroadcastedDeployAccountTransactionV0V1 { version, .. }, + BroadcastedDeployAccountTransaction::V1( + tx @ BroadcastedDeployAccountTransactionV1 { version, .. }, ) if version.without_query_version() == 0 => { context .sequencer @@ -136,8 +136,8 @@ pub(crate) async fn add_deploy_account_transaction_impl( )) .await } - BroadcastedDeployAccountTransaction::V0V1( - tx @ BroadcastedDeployAccountTransactionV0V1 { version, .. }, + BroadcastedDeployAccountTransaction::V1( + tx @ BroadcastedDeployAccountTransactionV1 { version, .. }, ) if version.without_query_version() == 1 => { context .sequencer @@ -153,7 +153,7 @@ pub(crate) async fn add_deploy_account_transaction_impl( )) .await } - BroadcastedDeployAccountTransaction::V0V1(_) => Err(SequencerError::StarknetError( + BroadcastedDeployAccountTransaction::V1(_) => Err(SequencerError::StarknetError( starknet_gateway_types::error::StarknetError { code: KnownStarknetErrorCode::InvalidTransactionVersion.into(), message: "".to_string(), @@ -264,31 +264,29 @@ mod tests { fn get_input() -> AddDeployAccountTransactionInput { AddDeployAccountTransactionInput { deploy_account_transaction: Transaction::DeployAccount( - BroadcastedDeployAccountTransaction::V0V1( - BroadcastedDeployAccountTransactionV0V1 { - version: TransactionVersion::ONE, - max_fee: fee!("0xbf391377813"), - signature: vec![ - transaction_signature_elem!( - "07dd3a55d94a0de6f3d6c104d7e6c88ec719a82f4e2bbc12587c8c187584d3d5" - ), - transaction_signature_elem!( - "071456dded17015d1234779889d78f3e7c763ddcfd2662b19e7843c7542614f8" - ), - ], - nonce: TransactionNonce::ZERO, - - contract_address_salt: contract_address_salt!( - "06d44a6aecb4339e23a9619355f101cf3cb9baec289fcd9fd51486655c1bb8a8" + BroadcastedDeployAccountTransaction::V1(BroadcastedDeployAccountTransactionV1 { + version: TransactionVersion::ONE, + max_fee: fee!("0xbf391377813"), + signature: vec![ + transaction_signature_elem!( + "07dd3a55d94a0de6f3d6c104d7e6c88ec719a82f4e2bbc12587c8c187584d3d5" ), - constructor_calldata: vec![call_param!( - "0677bb1cdc050e8d63855e8743ab6e09179138def390676cc03c484daf112ba1" - )], - class_hash: class_hash!( - "01fac3074c9d5282f0acc5c69a4781a1c711efea5e73c550c5d9fb253cf7fd3d" + transaction_signature_elem!( + "071456dded17015d1234779889d78f3e7c763ddcfd2662b19e7843c7542614f8" ), - }, - ), + ], + nonce: TransactionNonce::ZERO, + + contract_address_salt: contract_address_salt!( + "06d44a6aecb4339e23a9619355f101cf3cb9baec289fcd9fd51486655c1bb8a8" + ), + constructor_calldata: vec![call_param!( + "0677bb1cdc050e8d63855e8743ab6e09179138def390676cc03c484daf112ba1" + )], + class_hash: class_hash!( + "01fac3074c9d5282f0acc5c69a4781a1c711efea5e73c550c5d9fb253cf7fd3d" + ), + }), ), } } diff --git a/crates/storage/src/connection/transaction.rs b/crates/storage/src/connection/transaction.rs index ae98aae1d4..e51aa7467a 100644 --- a/crates/storage/src/connection/transaction.rs +++ b/crates/storage/src/connection/transaction.rs @@ -488,7 +488,7 @@ pub(crate) mod dto { use fake::{Dummy, Fake, Faker}; use pathfinder_common::*; use pathfinder_crypto::Felt; - use serde::{ser::SerializeSeq, Deserialize, Serialize}; + use serde::{Deserialize, Serialize}; /// Minimally encoded Felt value. #[derive(Clone, Debug, PartialEq, Eq, Default)] @@ -499,17 +499,9 @@ pub(crate) mod dto { where S: serde::Serializer, { - let len = self - .0 - .to_be_bytes() - .into_iter() - .skip_while(|&x| x == 0) - .count(); - let mut seq = serializer.serialize_seq(Some(len))?; - for elem in self.0.to_be_bytes().into_iter().skip_while(|&x| x == 0) { - seq.serialize_element(&elem)?; - } - seq.end() + let bytes = self.0.to_be_bytes(); + let zeros = bytes.iter().take_while(|&&x| x == 0).count(); + bytes[zeros..].serialize(serializer) } } From f46d11a9ac596693308c88057d8c168f63891c71 Mon Sep 17 00:00:00 2001 From: sistemd Date: Mon, 25 Mar 2024 09:19:09 +0100 Subject: [PATCH 24/24] use as_be_bytes an ci improvements --- .github/workflows/ci.yml | 1 - crates/storage/src/connection/transaction.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 562d68549d..032a617a95 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,7 +3,6 @@ name: CI on: workflow_dispatch: push: - pull_request: # Daily cron job used to clear and rebuild cache (https://github.com/Swatinem/rust-cache/issues/181). schedule: - cron: '0 0 * * *' diff --git a/crates/storage/src/connection/transaction.rs b/crates/storage/src/connection/transaction.rs index e51aa7467a..c85bce6189 100644 --- a/crates/storage/src/connection/transaction.rs +++ b/crates/storage/src/connection/transaction.rs @@ -499,7 +499,7 @@ pub(crate) mod dto { where S: serde::Serializer, { - let bytes = self.0.to_be_bytes(); + let bytes = self.0.as_be_bytes(); let zeros = bytes.iter().take_while(|&&x| x == 0).count(); bytes[zeros..].serialize(serializer) }