diff --git a/crates/torii/core/src/engine.rs b/crates/torii/core/src/engine.rs index 823a466d7a..8c7e7312e5 100644 --- a/crates/torii/core/src/engine.rs +++ b/crates/torii/core/src/engine.rs @@ -3,7 +3,7 @@ use std::time::Duration; use anyhow::Result; use dojo_world::contracts::world::WorldContractReader; use starknet::core::types::{ - BlockId, BlockWithTxs, Event, InvokeTransaction, InvokeTransactionReceipt, + BlockId, BlockWithTxs, Event, InvokeTransaction, InvokeTransactionReceipt, InvokeTransactionV1, MaybePendingBlockWithTxs, MaybePendingTransactionReceipt, Transaction, TransactionReceipt, }; use starknet::core::utils::get_selector_from_name; @@ -147,36 +147,48 @@ where _ => continue, }; - let receipt = match self + let receipt = self .provider .get_transaction_receipt(invoke_transaction.transaction_hash) .await - { - Ok(receipt) => receipt, + .ok() + .and_then(|receipt| match receipt { + MaybePendingTransactionReceipt::Receipt(TransactionReceipt::Invoke( + receipt, + )) => Some(receipt), + _ => None, + }); + + let invoke_receipt = match receipt { + Some(receipt) => receipt, _ => continue, }; - let receipt = match receipt { - MaybePendingTransactionReceipt::Receipt(receipt) => receipt, - _ => continue, - }; - - if let TransactionReceipt::Invoke(invoke_receipt) = receipt.clone() { - for (event_idx, event) in invoke_receipt.events.iter().enumerate() { - if event.from_address != self.world.address() { - continue; - } + let mut world_event = false; + for (event_idx, event) in invoke_receipt.events.iter().enumerate() { + if event.from_address != self.world.address() { + continue; + } - let event_id = format!( - "0x{:064x}:0x{:04x}:0x{:04x}", - block.block_number, tx_idx, event_idx - ); + world_event = true; + let event_id = + format!("0x{:064x}:0x{:04x}:0x{:04x}", block.block_number, tx_idx, event_idx); - Self::process_event(self, &block, &invoke_receipt, &event_id, event).await?; - } + Self::process_event(self, &block, &invoke_receipt, &event_id, event).await?; } - Self::process_transaction(self, &block, &receipt).await?; + if world_event { + let transaction_id = format!("0x{:064x}:0x{:04x}", block.block_number, tx_idx); + + Self::process_transaction( + self, + &block, + &invoke_receipt, + &transaction_id, + invoke_transaction, + ) + .await?; + } } info!("processed block: {}", block.block_number); @@ -194,10 +206,21 @@ where async fn process_transaction( &mut self, block: &BlockWithTxs, - receipt: &TransactionReceipt, + invoke_receipt: &InvokeTransactionReceipt, + transaction_id: &str, + transaction: &InvokeTransactionV1, ) -> Result<()> { for processor in &self.processors.transaction { - processor.process(self.db, self.provider.as_ref(), block, receipt).await? + processor + .process( + self.db, + self.provider.as_ref(), + block, + invoke_receipt, + transaction, + transaction_id, + ) + .await? } Ok(()) diff --git a/crates/torii/core/src/processors/mod.rs b/crates/torii/core/src/processors/mod.rs index 661990dc5b..528076ad20 100644 --- a/crates/torii/core/src/processors/mod.rs +++ b/crates/torii/core/src/processors/mod.rs @@ -1,7 +1,7 @@ use anyhow::{Error, Result}; use async_trait::async_trait; use dojo_world::contracts::world::WorldContractReader; -use starknet::core::types::{BlockWithTxs, Event, InvokeTransactionReceipt, TransactionReceipt}; +use starknet::core::types::{BlockWithTxs, Event, InvokeTransactionReceipt, InvokeTransactionV1}; use starknet::providers::Provider; use crate::sql::Sql; @@ -9,6 +9,7 @@ use crate::sql::Sql; pub mod metadata_update; pub mod register_model; pub mod store_set_record; +pub mod store_transaction; #[async_trait] pub trait EventProcessor

@@ -42,6 +43,8 @@ pub trait TransactionProcessor { db: &mut Sql, provider: &P, block: &BlockWithTxs, - transaction_receipt: &TransactionReceipt, + invoke_receipt: &InvokeTransactionReceipt, + transaction: &InvokeTransactionV1, + transaction_id: &str, ) -> Result<(), Error>; } diff --git a/crates/torii/core/src/processors/store_system_call.rs b/crates/torii/core/src/processors/store_system_call.rs deleted file mode 100644 index b0cfdb8c20..0000000000 --- a/crates/torii/core/src/processors/store_system_call.rs +++ /dev/null @@ -1,56 +0,0 @@ -use std::str::FromStr; - -use anyhow::{Error, Ok, Result}; -use async_trait::async_trait; -use starknet::core::types::{ - BlockWithTxs, FieldElement, InvokeTransaction, Transaction, TransactionReceipt, -}; -use starknet::core::utils::parse_cairo_short_string; -use starknet::providers::Provider; - -use super::TransactionProcessor; -use crate::sql::Sql; - -#[derive(Default)] -pub struct StoreSystemCallProcessor; - -const SYSTEM_NAME_OFFSET: usize = 6; -const ENTRYPOINT_OFFSET: usize = 2; -const EXECUTE_ENTRYPOINT: &str = - "0x240060cdb34fcc260f41eac7474ee1d7c80b7e3607daff9ac67c7ea2ebb1c44"; - -#[async_trait] -impl TransactionProcessor

for StoreSystemCallProcessor { - async fn process( - &self, - db: &Sql, - _provider: &P, - block: &BlockWithTxs, - transaction_receipt: &TransactionReceipt, - ) -> Result<(), Error> { - if let TransactionReceipt::Invoke(_) = transaction_receipt { - for tx in &block.transactions { - if let Some((tx_hash, system_name, calldata)) = parse_transaction(tx) { - let system_name = parse_cairo_short_string(&system_name)?; - - db.store_system_call(system_name, tx_hash, calldata).await?; - } - } - } - - Ok(()) - } -} - -fn parse_transaction( - transaction: &Transaction, -) -> Option<(FieldElement, FieldElement, &Vec)> { - if let Transaction::Invoke(InvokeTransaction::V1(tx)) = transaction { - let entrypoint_felt = FieldElement::from_str(EXECUTE_ENTRYPOINT).unwrap(); - if tx.calldata[ENTRYPOINT_OFFSET] == entrypoint_felt { - return Some((tx.transaction_hash, tx.calldata[SYSTEM_NAME_OFFSET], &tx.calldata)); - } - } - - None -} diff --git a/crates/torii/core/src/processors/store_transaction.rs b/crates/torii/core/src/processors/store_transaction.rs new file mode 100644 index 0000000000..ee4a4c6386 --- /dev/null +++ b/crates/torii/core/src/processors/store_transaction.rs @@ -0,0 +1,27 @@ +use anyhow::{Error, Ok, Result}; +use async_trait::async_trait; +use starknet::core::types::{BlockWithTxs, InvokeTransactionReceipt, InvokeTransactionV1}; +use starknet::providers::Provider; + +use super::TransactionProcessor; +use crate::sql::Sql; + +#[derive(Default)] +pub struct StoreTransactionProcessor; + +#[async_trait] +impl TransactionProcessor

for StoreTransactionProcessor { + async fn process( + &self, + db: &mut Sql, + _provider: &P, + _block: &BlockWithTxs, + _receipt: &InvokeTransactionReceipt, + transaction: &InvokeTransactionV1, + transaction_id: &str, + ) -> Result<(), Error> { + db.store_transaction(transaction, transaction_id); + + Ok(()) + } +} diff --git a/crates/torii/core/src/sql.rs b/crates/torii/core/src/sql.rs index 92c6a1e218..74b67d8655 100644 --- a/crates/torii/core/src/sql.rs +++ b/crates/torii/core/src/sql.rs @@ -8,7 +8,7 @@ use dojo_types::schema::Ty; use sqlx::pool::PoolConnection; use sqlx::sqlite::SqliteRow; use sqlx::{Executor, Pool, Row, Sqlite}; -use starknet::core::types::{Event, FieldElement}; +use starknet::core::types::{Event, FieldElement, InvokeTransactionV1}; use starknet_crypto::poseidon_hash_many; use super::World; @@ -200,20 +200,21 @@ impl Sql { Ok(rows.drain(..).map(|row| serde_json::from_str(&row.2).unwrap()).collect()) } - pub fn store_system_call( - &mut self, - system: String, - transaction_hash: FieldElement, - calldata: &[FieldElement], - ) { - let query = format!( - "INSERT OR IGNORE INTO system_calls (data, transaction_hash, system_id) VALUES ('{}', \ - '{:#x}', '{}')", - calldata.iter().map(|c| format!("{:#x}", c)).collect::>().join(","), - transaction_hash, - system + pub fn store_transaction(&mut self, transaction: &InvokeTransactionV1, transaction_id: &str) { + let txn_query = format!( + "INSERT OR IGNORE INTO transactions (id, transaction_hash, sender_address, calldata, \ + max_fee, signature, nonce) VALUES + ('{}', '{:#x}', '{}', '{}', '{:#x}', '{}', '{:#x}')", + transaction_id, + transaction.transaction_hash, + transaction.sender_address, + felts_sql_string(&transaction.calldata), + transaction.max_fee, + felts_sql_string(&transaction.signature), + transaction.nonce ); - self.query_queue.push(query); + + self.query_queue.push(txn_query); } pub fn store_event(&mut self, event_id: &str, event: &Event, transaction_hash: FieldElement) { diff --git a/crates/torii/graphql/src/mapping.rs b/crates/torii/graphql/src/mapping.rs index e387569093..7dd304c4e9 100644 --- a/crates/torii/graphql/src/mapping.rs +++ b/crates/torii/graphql/src/mapping.rs @@ -47,26 +47,31 @@ lazy_static! { TypeData::Simple(TypeRef::named(GraphqlType::DateTime.to_string())), ), ]); - pub static ref SYSTEM_CALL_TYPE_MAPPING: TypeMapping = IndexMap::from([ + pub static ref TRANSACTION_MAPPING: TypeMapping = IndexMap::from([ (Name::new("id"), TypeData::Simple(TypeRef::named(TypeRef::ID))), - (Name::new("transaction_hash"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), - (Name::new("data"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), - (Name::new("system_id"), TypeData::Simple(TypeRef::named(TypeRef::ID))), ( - Name::new("created_at"), - TypeData::Simple(TypeRef::named(GraphqlType::DateTime.to_string())), + Name::new("transaction_hash"), + TypeData::Simple(TypeRef::named(Primitive::Felt252(None).to_string())) ), - ]); - pub static ref SYSTEM_TYPE_MAPPING: TypeMapping = IndexMap::from([ - (Name::new("id"), TypeData::Simple(TypeRef::named(TypeRef::ID))), - (Name::new("name"), TypeData::Simple(TypeRef::named(TypeRef::STRING))), ( - Name::new("class_hash"), - TypeData::Simple(TypeRef::named(Primitive::Felt252(None).to_string())), + Name::new("sender_address"), + TypeData::Simple(TypeRef::named(Primitive::Felt252(None).to_string())) ), ( - Name::new("transaction_hash"), - TypeData::Simple(TypeRef::named(Primitive::Felt252(None).to_string())), + Name::new("calldata"), + TypeData::Simple(TypeRef::named_list(Primitive::Felt252(None).to_string())) + ), + ( + Name::new("max_fee"), + TypeData::Simple(TypeRef::named(Primitive::Felt252(None).to_string())) + ), + ( + Name::new("signature"), + TypeData::Simple(TypeRef::named_list(Primitive::Felt252(None).to_string())) + ), + ( + Name::new("nonce"), + TypeData::Simple(TypeRef::named(Primitive::Felt252(None).to_string())) ), ( Name::new("created_at"), diff --git a/crates/torii/graphql/src/object/event.rs b/crates/torii/graphql/src/object/event.rs index 855e2fbc5d..247963e5c6 100644 --- a/crates/torii/graphql/src/object/event.rs +++ b/crates/torii/graphql/src/object/event.rs @@ -4,12 +4,10 @@ use sqlx::{Pool, Sqlite}; use super::connection::{connection_arguments, connection_output, parse_connection_arguments}; use super::inputs::keys_input::{keys_argument, parse_keys_argument}; -use super::{ObjectTrait, TypeMapping, ValueMapping}; -use crate::mapping::{EVENT_TYPE_MAPPING, SYSTEM_CALL_TYPE_MAPPING}; +use super::{ObjectTrait, TypeMapping}; +use crate::mapping::EVENT_TYPE_MAPPING; use crate::query::constants::{EVENT_TABLE, ID_COLUMN}; -use crate::query::data::{count_rows, fetch_multiple_rows, fetch_single_row}; -use crate::query::value_mapping_from_row; -use crate::utils::extract; +use crate::query::data::{count_rows, fetch_multiple_rows}; pub struct EventObject; @@ -73,20 +71,4 @@ impl ObjectTrait for EventObject { Some(field) } - - fn related_fields(&self) -> Option> { - Some(vec![Field::new("systemCall", TypeRef::named_nn("SystemCall"), |ctx| { - FieldFuture::new(async move { - let mut conn = ctx.data::>()?.acquire().await?; - let event_values = ctx.parent_value.try_downcast_ref::()?; - let syscall_id = extract::(event_values, "system_call_id")?; - let data = - fetch_single_row(&mut conn, "system_calls", "id", &syscall_id.to_string()) - .await?; - let system_call = value_mapping_from_row(&data, &SYSTEM_CALL_TYPE_MAPPING, false)?; - - Ok(Some(Value::Object(system_call))) - }) - })]) - } } diff --git a/crates/torii/graphql/src/object/mod.rs b/crates/torii/graphql/src/object/mod.rs index d78107dab6..bffce6ece5 100644 --- a/crates/torii/graphql/src/object/mod.rs +++ b/crates/torii/graphql/src/object/mod.rs @@ -5,8 +5,7 @@ pub mod inputs; pub mod metadata; pub mod model; pub mod model_data; -pub mod system; -pub mod system_call; +pub mod transaction; use async_graphql::dynamic::{ Enum, Field, FieldFuture, InputObject, InputValue, Object, SubscriptionField, TypeRef, diff --git a/crates/torii/graphql/src/object/system.rs b/crates/torii/graphql/src/object/system.rs deleted file mode 100644 index 72b70d51cb..0000000000 --- a/crates/torii/graphql/src/object/system.rs +++ /dev/null @@ -1,46 +0,0 @@ -use async_graphql::dynamic::{Field, FieldFuture, TypeRef}; -use async_graphql::Value; -use sqlx::{Pool, Sqlite}; - -use super::{ObjectTrait, TypeMapping, ValueMapping}; -use crate::mapping::{SYSTEM_CALL_TYPE_MAPPING, SYSTEM_TYPE_MAPPING}; -use crate::query::constants::{SYSTEM_CALL_TABLE, SYSTEM_TABLE}; -use crate::query::data::fetch_single_row; -use crate::query::value_mapping_from_row; -use crate::utils::extract; - -pub struct SystemObject; - -impl ObjectTrait for SystemObject { - fn name(&self) -> (&str, &str) { - ("system", "systems") - } - - fn type_name(&self) -> &str { - "System" - } - - fn type_mapping(&self) -> &TypeMapping { - &SYSTEM_TYPE_MAPPING - } - - fn table_name(&self) -> Option<&str> { - Some(SYSTEM_TABLE) - } - - fn related_fields(&self) -> Option> { - Some(vec![Field::new("systemCalls", TypeRef::named_nn_list_nn("SystemCall"), |ctx| { - FieldFuture::new(async move { - let mut conn = ctx.data::>()?.acquire().await?; - let event_values = ctx.parent_value.try_downcast_ref::()?; - let syscall_id = extract::(event_values, "system_call_id")?; - let data = - fetch_single_row(&mut conn, SYSTEM_CALL_TABLE, "id", &syscall_id.to_string()) - .await?; - let system_call = value_mapping_from_row(&data, &SYSTEM_CALL_TYPE_MAPPING, false)?; - - Ok(Some(Value::Object(system_call))) - }) - })]) - } -} diff --git a/crates/torii/graphql/src/object/system_call.rs b/crates/torii/graphql/src/object/system_call.rs deleted file mode 100644 index e8557e8d5c..0000000000 --- a/crates/torii/graphql/src/object/system_call.rs +++ /dev/null @@ -1,44 +0,0 @@ -use async_graphql::dynamic::{Field, FieldFuture, TypeRef}; -use async_graphql::Value; -use sqlx::{Pool, Sqlite}; - -use super::{ObjectTrait, TypeMapping, ValueMapping}; -use crate::mapping::{SYSTEM_CALL_TYPE_MAPPING, SYSTEM_TYPE_MAPPING}; -use crate::query::constants::SYSTEM_CALL_TABLE; -use crate::query::data::fetch_single_row; -use crate::query::value_mapping_from_row; -use crate::utils::extract; - -pub struct SystemCallObject; - -impl ObjectTrait for SystemCallObject { - fn name(&self) -> (&str, &str) { - ("systemCall", "systemCalls") - } - - fn type_name(&self) -> &str { - "SystemCall" - } - - fn type_mapping(&self) -> &TypeMapping { - &SYSTEM_CALL_TYPE_MAPPING - } - - fn table_name(&self) -> Option<&str> { - Some(SYSTEM_CALL_TABLE) - } - - fn related_fields(&self) -> Option> { - Some(vec![Field::new("system", TypeRef::named_nn("System"), |ctx| { - FieldFuture::new(async move { - let mut conn = ctx.data::>()?.acquire().await?; - let syscall_values = ctx.parent_value.try_downcast_ref::()?; - let system_id = extract::(syscall_values, "system_id")?; - let system = fetch_single_row(&mut conn, "systems", "id", &system_id).await?; - let result = value_mapping_from_row(&system, &SYSTEM_TYPE_MAPPING, false)?; - - Ok(Some(Value::Object(result))) - }) - })]) - } -} diff --git a/crates/torii/graphql/src/object/transaction.rs b/crates/torii/graphql/src/object/transaction.rs new file mode 100644 index 0000000000..3ba04578d1 --- /dev/null +++ b/crates/torii/graphql/src/object/transaction.rs @@ -0,0 +1,23 @@ +use super::{ObjectTrait, TypeMapping}; +use crate::mapping::TRANSACTION_MAPPING; +use crate::query::constants::TRANSACTION_TABLE; + +pub struct TransactionObject; + +impl ObjectTrait for TransactionObject { + fn name(&self) -> (&str, &str) { + ("transaction", "transactions") + } + + fn type_name(&self) -> &str { + "Transaction" + } + + fn type_mapping(&self) -> &TypeMapping { + &TRANSACTION_MAPPING + } + + fn table_name(&self) -> Option<&str> { + Some(TRANSACTION_TABLE) + } +} diff --git a/crates/torii/graphql/src/query/constants.rs b/crates/torii/graphql/src/query/constants.rs index 49802ed183..0da5b346fc 100644 --- a/crates/torii/graphql/src/query/constants.rs +++ b/crates/torii/graphql/src/query/constants.rs @@ -4,8 +4,7 @@ pub const BOOLEAN_TRUE: i64 = 1; pub const ENTITY_TABLE: &str = "entities"; pub const EVENT_TABLE: &str = "events"; pub const MODEL_TABLE: &str = "models"; -pub const SYSTEM_CALL_TABLE: &str = "system_calls"; -pub const SYSTEM_TABLE: &str = "systems"; +pub const TRANSACTION_TABLE: &str = "transactions"; pub const METADATA_TABLE: &str = "metadata"; pub const ID_COLUMN: &str = "id"; diff --git a/crates/torii/graphql/src/schema.rs b/crates/torii/graphql/src/schema.rs index 7b7c5e3f47..b4dae3ea79 100644 --- a/crates/torii/graphql/src/schema.rs +++ b/crates/torii/graphql/src/schema.rs @@ -9,12 +9,11 @@ use super::object::connection::page_info::PageInfoObject; use super::object::entity::EntityObject; use super::object::event::EventObject; use super::object::model_data::ModelDataObject; -use super::object::system::SystemObject; -use super::object::system_call::SystemCallObject; use super::object::ObjectTrait; use super::types::ScalarType; use crate::object::metadata::MetadataObject; use crate::object::model::ModelObject; +use crate::object::transaction::TransactionObject; use crate::query::type_mapping_query; // The graphql schema is built dynamically at runtime, this is because we won't know the schema of @@ -107,12 +106,11 @@ async fn build_objects(pool: &SqlitePool) -> Result<(Vec>, // predefined objects let mut objects: Vec> = vec![ Box::new(EntityObject), + Box::new(EventObject), Box::new(MetadataObject), Box::new(ModelObject), - Box::new(SystemObject), - Box::new(EventObject), - Box::new(SystemCallObject), Box::new(PageInfoObject), + Box::new(TransactionObject), ]; // model union object diff --git a/crates/torii/migrations/20230316154230_setup.sql b/crates/torii/migrations/20230316154230_setup.sql index 254c351342..04ee400253 100644 --- a/crates/torii/migrations/20230316154230_setup.sql +++ b/crates/torii/migrations/20230316154230_setup.sql @@ -51,17 +51,6 @@ CREATE TABLE model_members( CREATE INDEX idx_model_members_model_id ON model_members (model_id); -CREATE TABLE system_calls ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - data TEXT NOT NULL, - transaction_hash TEXT NOT NULL, - system_id TEXT NOT NULL, - created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - UNIQUE (transaction_hash) -); - -CREATE INDEX idx_system_calls_created_at ON system_calls (created_at); - CREATE TABLE entities ( id TEXT NOT NULL PRIMARY KEY, keys TEXT, @@ -83,4 +72,28 @@ CREATE TABLE events ( created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); -CREATE INDEX idx_events_keys ON events (keys); \ No newline at end of file +CREATE INDEX idx_events_keys ON events (keys); + +CREATE TABLE transactions ( + id TEXT NOT NULL PRIMARY KEY, + transaction_hash TEXT NOT NULL, + sender_address TEXT NOT NULL, + calldata TEXT NOT NULL, + max_fee TEXT NOT NULL, + signature TEXT NOT NULL, + nonce TEXT NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE (transaction_hash) +); + +CREATE TABLE transaction_receipts ( + id TEXT NOT NULL PRIMARY KEY, + transaction_hash TEXT NOT NULL, + actual_fee TEXT NOT NULL, + finality_status TEXT NOT NULL, + block_hash TEXT NOT NULL, + block_number INTEGER NOT NULL, + execution_result TEXT NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE (transaction_hash) +); diff --git a/crates/torii/server/src/cli.rs b/crates/torii/server/src/cli.rs index d5a74637b5..859fdfd792 100644 --- a/crates/torii/server/src/cli.rs +++ b/crates/torii/server/src/cli.rs @@ -15,6 +15,7 @@ use torii_core::engine::{Engine, EngineConfig, Processors}; use torii_core::processors::metadata_update::MetadataUpdateProcessor; use torii_core::processors::register_model::RegisterModelProcessor; use torii_core::processors::store_set_record::StoreSetRecordProcessor; +use torii_core::processors::store_transaction::StoreTransactionProcessor; use torii_core::sql::Sql; use tracing::error; use tracing_subscriber::fmt; @@ -96,7 +97,7 @@ async fn main() -> anyhow::Result<()> { Box::new(StoreSetRecordProcessor), Box::new(MetadataUpdateProcessor), ], - // transaction: vec![Box::new(StoreSystemCallProcessor)], + transaction: vec![Box::new(StoreTransactionProcessor)], ..Processors::default() };