From 2fc1955f897d0d6b5bd53c5b89fe2388de863b2a Mon Sep 17 00:00:00 2001 From: Yael Doweck Date: Thu, 19 Dec 2024 15:59:45 +0200 Subject: [PATCH] feat(consensus): add central invoke transaction --- Cargo.lock | 2 + .../papyrus_consensus_orchestrator/Cargo.toml | 2 + .../resources/central_invoke_tx.json | 34 +++++++ .../src/cende/central_objects.rs | 98 +++++++++++++++++++ .../src/cende/central_objects_test.rs | 70 +++++++++++-- 5 files changed, 200 insertions(+), 6 deletions(-) create mode 100644 crates/sequencing/papyrus_consensus_orchestrator/resources/central_invoke_tx.json diff --git a/Cargo.lock b/Cargo.lock index a47052fbd3..8dae0c5feb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7394,6 +7394,7 @@ dependencies = [ name = "papyrus_consensus_orchestrator" version = "0.0.0" dependencies = [ + "assert_matches", "async-trait", "blockifier", "chrono", @@ -7407,6 +7408,7 @@ dependencies = [ "papyrus_protobuf", "papyrus_storage", "papyrus_test_utils", + "rstest", "serde", "serde_json", "starknet-types-core", diff --git a/crates/sequencing/papyrus_consensus_orchestrator/Cargo.toml b/crates/sequencing/papyrus_consensus_orchestrator/Cargo.toml index b6f66304b2..885132e6fb 100644 --- a/crates/sequencing/papyrus_consensus_orchestrator/Cargo.toml +++ b/crates/sequencing/papyrus_consensus_orchestrator/Cargo.toml @@ -7,6 +7,7 @@ license-file.workspace = true description = "Implements the consensus context and orchestrates the node's components accordingly" [dependencies] +assert_matches.workspace = true async-trait.workspace = true blockifier.workspace = true chrono.workspace = true @@ -31,6 +32,7 @@ mockall.workspace = true papyrus_network = { workspace = true, features = ["testing"] } papyrus_storage = { workspace = true, features = ["testing"] } papyrus_test_utils.workspace = true +rstest.workspace = true serde_json.workspace = true starknet_batcher_types = { workspace = true, features = ["testing"] } test-case.workspace = true diff --git a/crates/sequencing/papyrus_consensus_orchestrator/resources/central_invoke_tx.json b/crates/sequencing/papyrus_consensus_orchestrator/resources/central_invoke_tx.json new file mode 100644 index 0000000000..392c384477 --- /dev/null +++ b/crates/sequencing/papyrus_consensus_orchestrator/resources/central_invoke_tx.json @@ -0,0 +1,34 @@ +{ + "tx": { + "hash_value": "0x6efd067c859e6469d0f6d158e9ae408a9552eb8cc11f618ab3aef3e52450666", + "version": "0x3", + "signature": [], + "nonce": "0x0", + "sender_address": "0x14abfd58671a1a9b30de2fcd2a42e8bff2ce1096a7c70bc7995904965f277e", + "nonce_data_availability_mode": 0, + "fee_data_availability_mode": 0, + "resource_bounds": { + "L1_GAS": { + "max_amount": "0x1", + "max_price_per_unit": "0x1" + }, + "L2_GAS": { + "max_amount": "0x0", + "max_price_per_unit": "0x0" + }, + "L1_DATA": { + "max_amount": "0x0", + "max_price_per_unit": "0x0" + } + }, + "tip": "0x0", + "paymaster_data": [], + "calldata": [ + "0x0", + "0x1" + ], + "account_deployment_data": [], + "type": "INVOKE_FUNCTION" + }, + "time_created": 1734601615 +} diff --git a/crates/sequencing/papyrus_consensus_orchestrator/src/cende/central_objects.rs b/crates/sequencing/papyrus_consensus_orchestrator/src/cende/central_objects.rs index 941c4d6376..2ae03e24f5 100644 --- a/crates/sequencing/papyrus_consensus_orchestrator/src/cende/central_objects.rs +++ b/crates/sequencing/papyrus_consensus_orchestrator/src/cende/central_objects.rs @@ -1,3 +1,4 @@ +use assert_matches::assert_matches; use blockifier::state::cached_state::CommitmentStateDiff; use indexmap::{indexmap, IndexMap}; use serde::{Deserialize, Serialize}; @@ -10,7 +11,17 @@ use starknet_api::block::{ }; use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce}; use starknet_api::data_availability::DataAvailabilityMode; +use starknet_api::executable_transaction::{AccountTransaction, InvokeTransaction, Transaction}; use starknet_api::state::StorageKey; +use starknet_api::transaction::fields::{ + AccountDeploymentData, + Calldata, + PaymasterData, + Tip, + TransactionSignature, + ValidResourceBounds, +}; +use starknet_api::transaction::TransactionHash; use starknet_types_core::felt::Felt; /// Central objects are required in order to continue processing the block by the centralized @@ -89,3 +100,90 @@ impl From<(CommitmentStateDiff, BlockInfo, StarknetVersion)> for CentralStateDif } } } + +#[derive(Debug, PartialEq, Deserialize, Serialize)] +pub struct CentralInvokeTransactionV3 { + pub sender_address: ContractAddress, + pub calldata: Calldata, + pub signature: TransactionSignature, + pub nonce: Nonce, + // TODO(yael): Consider defining a type for resource_bounds that matches the python object. + pub resource_bounds: ValidResourceBounds, + pub tip: Tip, + pub paymaster_data: PaymasterData, + pub account_deployment_data: AccountDeploymentData, + pub nonce_data_availability_mode: u8, + pub fee_data_availability_mode: u8, + pub hash_value: TransactionHash, +} + +impl From for CentralInvokeTransactionV3 { + fn from(tx: InvokeTransaction) -> CentralInvokeTransactionV3 { + assert_matches!(tx.tx, starknet_api::transaction::InvokeTransaction::V3(_)); + CentralInvokeTransactionV3 { + sender_address: tx.sender_address(), + calldata: tx.calldata(), + signature: tx.signature(), + nonce: tx.nonce(), + resource_bounds: tx.resource_bounds(), + tip: tx.tip(), + paymaster_data: tx.paymaster_data(), + account_deployment_data: tx.account_deployment_data(), + nonce_data_availability_mode: into_u8(tx.nonce_data_availability_mode()), + fee_data_availability_mode: into_u8(tx.fee_data_availability_mode()), + hash_value: tx.tx_hash(), + } + } +} + +fn into_u8(data_availability_mode: DataAvailabilityMode) -> u8 { + match data_availability_mode { + DataAvailabilityMode::L1 => 0, + DataAvailabilityMode::L2 => 1, + } +} + +#[derive(Debug, PartialEq, Deserialize, Serialize)] +#[serde(tag = "version")] +pub enum CentralInvokeTransaction { + #[serde(rename = "0x3")] + V3(CentralInvokeTransactionV3), +} + +#[derive(Debug, PartialEq, Deserialize, Serialize)] +#[serde(tag = "type")] +pub enum CentralTransaction { + #[serde(rename = "INVOKE_FUNCTION")] + Invoke(CentralInvokeTransaction), +} + +impl From for CentralTransaction { + fn from(tx: Transaction) -> CentralTransaction { + match tx { + Transaction::Account(AccountTransaction::Invoke(invoke_tx)) => { + CentralTransaction::Invoke(CentralInvokeTransaction::V3( + CentralInvokeTransactionV3::from(invoke_tx), + )) + } + Transaction::Account(_) => unimplemented!(), + Transaction::L1Handler(_) => unimplemented!(), + } + } +} + +#[derive(Debug, PartialEq, Deserialize, Serialize)] +pub struct CentralTransactionWritten { + pub tx: CentralTransaction, + pub time_created: u64, +} + +impl From<(Transaction, u64)> for CentralTransactionWritten { + fn from((tx, timestamp): (Transaction, u64)) -> CentralTransactionWritten { + CentralTransactionWritten { + tx: CentralTransaction::from(tx), + // This timestamp is required for metrics data. Yoni and Noa approved that it is + // sufficient to take the time during the batcher run. + time_created: timestamp, + } + } +} diff --git a/crates/sequencing/papyrus_consensus_orchestrator/src/cende/central_objects_test.rs b/crates/sequencing/papyrus_consensus_orchestrator/src/cende/central_objects_test.rs index 098a310fd5..ffc80c1eeb 100644 --- a/crates/sequencing/papyrus_consensus_orchestrator/src/cende/central_objects_test.rs +++ b/crates/sequencing/papyrus_consensus_orchestrator/src/cende/central_objects_test.rs @@ -1,4 +1,8 @@ +use std::sync::Arc; + use indexmap::indexmap; +use rstest::rstest; +use serde_json::Value; use starknet_api::block::{ BlockNumber, BlockTimestamp, @@ -8,14 +12,32 @@ use starknet_api::block::{ }; use starknet_api::core::{ClassHash, CompiledClassHash, Nonce}; use starknet_api::data_availability::DataAvailabilityMode; +use starknet_api::executable_transaction::InvokeTransaction; +use starknet_api::execution_resources::GasAmount; use starknet_api::test_utils::read_json_file; +use starknet_api::transaction::fields::{ + AllResourceBounds, + Calldata, + ResourceBounds, + ValidResourceBounds, +}; +use starknet_api::transaction::{InvokeTransactionV3, TransactionHash}; use starknet_api::{contract_address, felt, storage_key}; -use super::{CentralBlockInfo, CentralResourcePrice, CentralStateDiff}; +use super::{ + CentralBlockInfo, + CentralInvokeTransaction, + CentralResourcePrice, + CentralStateDiff, + CentralTransaction, + CentralTransactionWritten, +}; pub const CENTRAL_STATE_DIFF_JSON_PATH: &str = "central_state_diff.json"; +pub const CENTRAL_INVOKE_TX_JSON_PATH: &str = "central_invoke_tx.json"; fn central_state_diff() -> CentralStateDiff { + // TODO(yael): compute the CentralStateDiff with into(). CentralStateDiff { address_to_class_hash: indexmap! { contract_address!(1_u8) => @@ -52,12 +74,48 @@ fn central_state_diff() -> CentralStateDiff { } } -#[test] -fn serialize_central_state_diff() { - let rust_central_state_diff = central_state_diff(); - let rust_json = serde_json::to_value(&rust_central_state_diff).unwrap(); +fn central_invoke_transaction_json() -> Value { + let invoke_tx = InvokeTransaction { + tx: starknet_api::transaction::InvokeTransaction::V3(InvokeTransactionV3 { + resource_bounds: ValidResourceBounds::AllResources(AllResourceBounds { + l1_gas: ResourceBounds { + max_amount: GasAmount(1), + max_price_per_unit: GasPrice(1), + }, + l2_gas: ResourceBounds::default(), + l1_data_gas: ResourceBounds::default(), + }), + // TODO(yael): consider testing these fields with non-default values + tip: Default::default(), + signature: Default::default(), + nonce: Default::default(), + sender_address: contract_address!( + "0x14abfd58671a1a9b30de2fcd2a42e8bff2ce1096a7c70bc7995904965f277e" + ), + calldata: Calldata(Arc::new(vec![felt!(0_u8), felt!(1_u8)])), + nonce_data_availability_mode: DataAvailabilityMode::L1, + fee_data_availability_mode: DataAvailabilityMode::L1, + paymaster_data: Default::default(), + account_deployment_data: Default::default(), + }), + tx_hash: TransactionHash(felt!( + "0x6efd067c859e6469d0f6d158e9ae408a9552eb8cc11f618ab3aef3e52450666" + )), + }; + + let central_transaction_written = CentralTransactionWritten { + tx: CentralTransaction::Invoke(CentralInvokeTransaction::V3(invoke_tx.into())), + time_created: 1734601615, + }; + + serde_json::to_value(central_transaction_written).unwrap() +} - let python_json = read_json_file(CENTRAL_STATE_DIFF_JSON_PATH); +#[rstest] +#[case::state_diff(serde_json::to_value(central_state_diff()).unwrap(), CENTRAL_STATE_DIFF_JSON_PATH)] +#[case::invoke_tx(central_invoke_transaction_json(), CENTRAL_INVOKE_TX_JSON_PATH)] +fn serialize_central_objects(#[case] rust_json: Value, #[case] python_json_path: &str) { + let python_json = read_json_file(python_json_path); assert_eq!(rust_json, python_json,); }