diff --git a/Cargo.lock b/Cargo.lock index 67d9423302..cc51826872 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -553,20 +553,6 @@ dependencies = [ "tower-service", ] -[[package]] -name = "backoff" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" -dependencies = [ - "futures-core", - "getrandom 0.2.10", - "instant", - "pin-project-lite", - "rand 0.8.5", - "tokio", -] - [[package]] name = "backtrace" version = "0.3.69" @@ -7318,7 +7304,6 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", - "backoff", "bcs", "cached", "chrono", diff --git a/Cargo.toml b/Cargo.toml index d26a6dd838..6d8a895c78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -224,13 +224,6 @@ diesel = { version = "2.1.0", features = [ diesel-derive-enum = { version = "2.0.1", features = ["sqlite"] } diesel_migrations = { version = "2.0.0" } tap = "1.0.1" -backoff = { version = "0.4.0", features = [ - "futures", - "futures-core", - "pin-project-lite", - "tokio", - "tokio_1", -] } dotenvy = "0.15" sized-chunks = { version = "0.6" } diff --git a/crates/rooch-indexer/Cargo.toml b/crates/rooch-indexer/Cargo.toml index 8f2fdb2cba..ec51a1580c 100644 --- a/crates/rooch-indexer/Cargo.toml +++ b/crates/rooch-indexer/Cargo.toml @@ -33,7 +33,6 @@ serde_json = { workspace = true } regex = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } -backoff = { workspace = true } tokio = { workspace = true, features = ["full"] } diesel = { workspace = true } diesel-derive-enum = { workspace = true } diff --git a/crates/rooch-indexer/migrations/2023-10-31-085226_transactions/up.sql b/crates/rooch-indexer/migrations/2023-10-31-085226_transactions/up.sql index e3334dcaf7..9928a463b1 100644 --- a/crates/rooch-indexer/migrations/2023-10-31-085226_transactions/up.sql +++ b/crates/rooch-indexer/migrations/2023-10-31-085226_transactions/up.sql @@ -4,7 +4,8 @@ CREATE TABLE transactions ( transaction_type VARCHAR NOT NULL, sequence_number BIGINT NOT NULL, multichain_id BIGINT NOT NULL, - multichain_raw_address VARCHAR NOT NULL, + multichain_address VARCHAR NOT NULL, + multichain_original_address VARCHAR NOT NULL, sender VARCHAR NOT NULL, action VARCHAR NOT NULL, action_type SMALLINT NOT NULL, diff --git a/crates/rooch-indexer/src/actor/indexer.rs b/crates/rooch-indexer/src/actor/indexer.rs index 070d83a738..c1f32c8163 100644 --- a/crates/rooch-indexer/src/actor/indexer.rs +++ b/crates/rooch-indexer/src/actor/indexer.rs @@ -3,6 +3,7 @@ use crate::actor::messages::{ IndexerEventsMessage, IndexerTransactionMessage, QueryIndexerEventsMessage, + QueryIndexerTransactionsMessage, }; use crate::indexer_reader::IndexerReader; use crate::store::traits::IndexerStoreTrait; @@ -12,6 +13,7 @@ use anyhow::{anyhow, Result}; use async_trait::async_trait; use coerce::actor::{context::ActorContext, message::Handler, Actor}; use rooch_types::indexer::event_filter::IndexerEvent; +use rooch_types::transaction::TransactionWithInfo; pub struct IndexerActor { indexer_store: IndexerStore, @@ -45,9 +47,8 @@ impl Handler for IndexerActor { let indexed_transaction = IndexedTransaction::new(transaction, sequence_info, execution_info, moveos_tx)?; - let _transactions = vec![indexed_transaction]; - //TODO Open after supporting automatic creation of sqlite schema - // self.indexer_store.persist_transactions(transactions)?; + let transactions = vec![indexed_transaction]; + self.indexer_store.persist_transactions(transactions)?; Ok(()) } } @@ -78,6 +79,25 @@ impl Handler for IndexerActor { } } +#[async_trait] +impl Handler for IndexerActor { + async fn handle( + &mut self, + msg: QueryIndexerTransactionsMessage, + _ctx: &mut ActorContext, + ) -> Result> { + let QueryIndexerTransactionsMessage { + filter, + cursor, + limit, + descending_order, + } = msg; + self.indexer_reader + .query_transactions_with_filter(filter, cursor, limit, descending_order) + .map_err(|e| anyhow!(format!("Failed to query indexer transactions: {:?}", e))) + } +} + #[async_trait] impl Handler for IndexerActor { async fn handle( diff --git a/crates/rooch-indexer/src/actor/messages.rs b/crates/rooch-indexer/src/actor/messages.rs index 7845e39a95..77ab32c8d3 100644 --- a/crates/rooch-indexer/src/actor/messages.rs +++ b/crates/rooch-indexer/src/actor/messages.rs @@ -6,7 +6,8 @@ use coerce::actor::message::Message; use moveos_types::moveos_std::event::Event; use moveos_types::transaction::{TransactionExecutionInfo, VerifiedMoveOSTransaction}; use rooch_types::indexer::event_filter::{EventFilter, IndexerEvent, IndexerEventID}; -use rooch_types::transaction::{TransactionSequenceInfo, TypedTransaction}; +use rooch_types::indexer::transaction_filter::TransactionFilter; +use rooch_types::transaction::{TransactionSequenceInfo, TransactionWithInfo, TypedTransaction}; use serde::{Deserialize, Serialize}; /// Indexer Transaction write Message @@ -35,6 +36,20 @@ impl Message for IndexerEventsMessage { type Result = Result<()>; } +/// Query Indexer Transactions Message +#[derive(Debug, Serialize, Deserialize)] +pub struct QueryIndexerTransactionsMessage { + pub filter: TransactionFilter, + // exclusive cursor if `Some`, otherwise start from the beginning + pub cursor: Option, + pub limit: usize, + pub descending_order: bool, +} + +impl Message for QueryIndexerTransactionsMessage { + type Result = Result>; +} + /// Query Indexer Events Message #[derive(Debug, Serialize, Deserialize)] pub struct QueryIndexerEventsMessage { diff --git a/crates/rooch-indexer/src/indexer_reader.rs b/crates/rooch-indexer/src/indexer_reader.rs index b06a87c6d1..2deee13c06 100644 --- a/crates/rooch-indexer/src/indexer_reader.rs +++ b/crates/rooch-indexer/src/indexer_reader.rs @@ -13,18 +13,22 @@ use diesel::{ use std::ops::DerefMut; use crate::models::events::StoredEvent; -use crate::schema::events; +use crate::schema::{events, transactions}; use rooch_types::indexer::event_filter::{EventFilter, IndexerEvent, IndexerEventID}; +use rooch_types::indexer::transaction_filter::TransactionFilter; use rooch_types::transaction::TransactionWithInfo; +pub const TX_ORDER_STR: &str = "tx_order"; +pub const TX_HASH_STR: &str = "tx_hash"; +pub const TX_SENDER_STR: &str = "sender"; +pub const CREATED_AT_STR: &str = "created_at"; + +pub const TRANSACTION_ORIGINAL_ADDRESS_STR: &str = "multichain_original_address"; + pub const EVENT_HANDLE_ID_STR: &str = "event_handle_id"; pub const EVENT_INDEX_STR: &str = "event_index"; pub const EVENT_SEQ_STR: &str = "event_seq"; -pub const TX_ORDER_STR: &str = "tx_order"; -pub const TX_HASH_STR: &str = "tx_hash"; -pub const EVENT_SENDER_STR: &str = "sender"; pub const EVENT_TYPE_STR: &str = "event_type"; -pub const EVENT_CREATED_TIME_STR: &str = "created_time"; #[derive(Clone)] pub(crate) struct InnerIndexerReader { @@ -100,14 +104,97 @@ impl IndexerReader { }) } - pub fn stored_transaction_to_transaction_block( + pub fn query_transactions_with_filter( &self, - stored_transactions: Vec, + filter: TransactionFilter, + cursor: Option, + limit: usize, + descending_order: bool, ) -> IndexerResult> { - stored_transactions + let tx_order = if let Some(cursor) = cursor { + cursor as i64 + } else if descending_order { + let max_tx_order: i64 = self.inner_indexer_reader.run_query(|conn| { + transactions::dsl::transactions + .select(transactions::tx_order) + .order_by(transactions::tx_order.desc()) + .first::(conn) + })?; + max_tx_order + 1 + } else { + -1 + }; + + let main_where_clause = match filter { + TransactionFilter::Sender(sender) => { + format!("{TX_SENDER_STR} = \"{}\"", sender.to_hex_literal()) + } + TransactionFilter::OriginalAddress(address) => { + format!("{TRANSACTION_ORIGINAL_ADDRESS_STR} = \"{}\"", address) + } + TransactionFilter::TxHashes(tx_hashes) => { + let in_tx_hashes_str: String = tx_hashes + .iter() + .map(|tx_hash| format!("\"{:?}\"", tx_hash)) + .collect::>() + .join(","); + format!("{TX_HASH_STR} in ({})", in_tx_hashes_str) + } + TransactionFilter::TimeRange { + start_time, + end_time, + } => { + format!( + "({CREATED_AT_STR} >= {} AND {CREATED_AT_STR} < {})", + start_time, end_time + ) + } + TransactionFilter::TxOrderRange { + from_order, + to_order, + } => { + format!( + "({TX_ORDER_STR} >= {} AND {TX_ORDER_STR} < {})", + from_order, to_order + ) + } + }; + + let cursor_clause = if descending_order { + format!("AND ({TX_ORDER_STR} < {})", tx_order) + } else { + format!("AND ({TX_ORDER_STR} > {})", tx_order) + }; + let order_clause = if descending_order { + format!("{TX_ORDER_STR} DESC") + } else { + format!("{TX_ORDER_STR} ASC") + }; + + let query = format!( + " + SELECT * FROM transactions \ + WHERE {} {} \ + ORDER BY {} \ + LIMIT {} + ", + main_where_clause, cursor_clause, order_clause, limit, + ); + + tracing::debug!("query transactions: {}", query); + let stored_transactions = self + .inner_indexer_reader + .run_query(|conn| diesel::sql_query(query).load::(conn))?; + + let result = stored_transactions .into_iter() - .map(|stored_transaction| stored_transaction.try_into_transaction_with_info()) - .collect::>>() + .map(|t| t.try_into_transaction_with_info()) + .collect::>>() + .map_err(|e| { + IndexerError::SQLiteReadError(format!("Cast indexer transactions failed: {:?}", e)) + })?; + + Ok(result) } pub fn query_events_with_filter( @@ -122,7 +209,7 @@ impl IndexerReader { tx_order, event_index, } = cursor; - (tx_order as i128, event_index as i64) + (tx_order as i64, event_index as i64) } else if descending_order { let (max_tx_order, event_index): (i64, i64) = self.inner_indexer_reader.run_query(|conn| { @@ -131,7 +218,7 @@ impl IndexerReader { .order_by((events::tx_order.desc(), events::event_index.desc())) .first::<(i64, i64)>(conn) })?; - ((max_tx_order as i128) + 1, event_index) + (max_tx_order + 1, event_index) } else { (-1, 0) }; @@ -142,7 +229,7 @@ impl IndexerReader { format!("{EVENT_TYPE_STR} = \"{}\"", event_type_str) } EventFilter::Sender(sender) => { - format!("{EVENT_SENDER_STR} = \"{}\"", sender.to_hex_literal()) + format!("{TX_SENDER_STR} = \"{}\"", sender.to_hex_literal()) } EventFilter::TxHash(tx_hash) => { let tx_hash_str = format!("{:?}", tx_hash); @@ -153,7 +240,7 @@ impl IndexerReader { end_time, } => { format!( - "({EVENT_CREATED_TIME_STR} >= {} AND {EVENT_CREATED_TIME_STR} < {})", + "({CREATED_AT_STR} >= {} AND {CREATED_AT_STR} < {})", start_time, end_time ) } diff --git a/crates/rooch-indexer/src/models/transactions.rs b/crates/rooch-indexer/src/models/transactions.rs index 50fd00c7f2..801c322b37 100644 --- a/crates/rooch-indexer/src/models/transactions.rs +++ b/crates/rooch-indexer/src/models/transactions.rs @@ -7,9 +7,8 @@ use moveos_types::h256::H256; use std::str::FromStr; use crate::schema::transactions; -use crate::types::{IndexedTransaction, IndexerResult}; +use crate::types::IndexedTransaction; -use crate::errors::IndexerError; use moveos_types::transaction::TransactionExecutionInfo; use rooch_types::transaction::authenticator::Authenticator; use rooch_types::transaction::{RawTransaction, TransactionType, TransactionWithInfo}; @@ -33,7 +32,9 @@ pub struct StoredTransaction { #[diesel(sql_type = diesel::sql_types::BigInt)] pub multichain_id: i64, #[diesel(sql_type = diesel::sql_types::Text)] - pub multichain_raw_address: String, + pub multichain_address: String, + #[diesel(sql_type = diesel::sql_types::Text)] + pub multichain_original_address: String, /// the rooch address of sender who send the transaction #[diesel(sql_type = diesel::sql_types::Text)] pub sender: String, @@ -76,26 +77,26 @@ pub struct StoredTransaction { impl From for StoredTransaction { fn from(transaction: IndexedTransaction) -> Self { StoredTransaction { - tx_hash: transaction.tx_hash.to_string(), + tx_hash: format!("{:?}", transaction.tx_hash), tx_order: transaction.tx_order as i64, transaction_type: transaction.transaction_type.transaction_type_name(), sequence_number: transaction.sequence_number as i64, multichain_id: transaction.multichain_id.id() as i64, - multichain_raw_address: transaction.multichain_raw_address, - sender: transaction.sender.to_string(), + multichain_address: transaction.multichain_address, + multichain_original_address: transaction.multichain_original_address, + sender: transaction.sender.to_hex_literal(), action: transaction.action.action_name(), action_type: transaction.action.action_type() as i16, action_raw: transaction.action_raw, auth_validator_id: transaction.auth_validator_id as i64, authenticator_payload: transaction.authenticator_payload, - tx_accumulator_root: transaction.tx_accumulator_root.to_string(), + tx_accumulator_root: format!("{:?}", transaction.tx_accumulator_root), transaction_raw: transaction.transaction_raw, - state_root: transaction.state_root.to_string(), - event_root: transaction.event_root.to_string(), + state_root: format!("{:?}", transaction.state_root), + event_root: format!("{:?}", transaction.event_root), gas_used: transaction.gas_used as i64, - // TODO how to index and display the vm status ? - status: transaction.status.to_string(), + status: transaction.status, tx_order_auth_validator_id: transaction.tx_order_auth_validator_id as i64, tx_order_authenticator_payload: transaction.tx_order_authenticator_payload, @@ -106,10 +107,9 @@ impl From for StoredTransaction { } impl StoredTransaction { - pub fn try_into_transaction_with_info(self) -> IndexerResult { - //TODO construct TypedTransaction + pub fn try_into_transaction_with_info(self) -> Result { let raw_transaction = RawTransaction { - transaction_type: TransactionType::Rooch, + transaction_type: TransactionType::from_str(self.transaction_type.as_str())?, raw: self.transaction_raw, }; let transaction = TypedTransaction::try_from(raw_transaction)?; @@ -119,19 +119,16 @@ impl StoredTransaction { auth_validator_id: self.tx_order_auth_validator_id as u64, payload: self.tx_order_authenticator_payload, }, - tx_accumulator_root: H256::from_str(self.tx_accumulator_root.as_str()) - .map_err(|e| IndexerError::DataTransformationError(e.to_string()))?, + tx_accumulator_root: H256::from_str(self.tx_accumulator_root.as_str())?, }; + + let status: KeptVMStatus = serde_json::from_str(self.status.as_str())?; let execution_info = TransactionExecutionInfo { - tx_hash: H256::from_str(self.tx_hash.as_str()) - .map_err(|e| IndexerError::DataTransformationError(e.to_string()))?, - state_root: H256::from_str(self.state_root.as_str()) - .map_err(|e| IndexerError::DataTransformationError(e.to_string()))?, - event_root: H256::from_str(self.state_root.as_str()) - .map_err(|e| IndexerError::DataTransformationError(e.to_string()))?, + tx_hash: H256::from_str(self.tx_hash.as_str())?, + state_root: H256::from_str(self.state_root.as_str())?, + event_root: H256::from_str(self.state_root.as_str())?, gas_used: self.gas_used as u64, - //TODO convert KeptVMStatus - status: KeptVMStatus::Executed, + status, }; Ok(TransactionWithInfo { transaction, diff --git a/crates/rooch-indexer/src/proxy/mod.rs b/crates/rooch-indexer/src/proxy/mod.rs index 1fad58c817..27583030c3 100644 --- a/crates/rooch-indexer/src/proxy/mod.rs +++ b/crates/rooch-indexer/src/proxy/mod.rs @@ -4,13 +4,15 @@ use crate::actor::indexer::IndexerActor; use crate::actor::messages::{ IndexerEventsMessage, IndexerTransactionMessage, QueryIndexerEventsMessage, + QueryIndexerTransactionsMessage, }; use anyhow::Result; use coerce::actor::ActorRef; use moveos_types::moveos_std::event::Event; use moveos_types::transaction::{TransactionExecutionInfo, VerifiedMoveOSTransaction}; use rooch_types::indexer::event_filter::{EventFilter, IndexerEvent, IndexerEventID}; -use rooch_types::transaction::{TransactionSequenceInfo, TypedTransaction}; +use rooch_types::indexer::transaction_filter::TransactionFilter; +use rooch_types::transaction::{TransactionSequenceInfo, TransactionWithInfo, TypedTransaction}; #[derive(Clone)] pub struct IndexerProxy { @@ -56,6 +58,24 @@ impl IndexerProxy { .await? } + pub async fn query_transactions( + &self, + filter: TransactionFilter, + // exclusive cursor if `Some`, otherwise start from the beginning + cursor: Option, + limit: usize, + descending_order: bool, + ) -> Result> { + self.actor + .send(QueryIndexerTransactionsMessage { + filter, + cursor, + limit, + descending_order, + }) + .await? + } + pub async fn query_events( &self, filter: EventFilter, diff --git a/crates/rooch-indexer/src/schema.rs b/crates/rooch-indexer/src/schema.rs index fbb9eabbb5..8aa7544838 100644 --- a/crates/rooch-indexer/src/schema.rs +++ b/crates/rooch-indexer/src/schema.rs @@ -24,7 +24,8 @@ diesel::table! { transaction_type -> Text, sequence_number -> BigInt, multichain_id -> BigInt, - multichain_raw_address -> Text, + multichain_address -> Text, + multichain_original_address -> Text, sender -> Text, action -> Text, action_type -> SmallInt, diff --git a/crates/rooch-indexer/src/store/sqlite_store.rs b/crates/rooch-indexer/src/store/sqlite_store.rs index c514fde228..fa431c6afc 100644 --- a/crates/rooch-indexer/src/store/sqlite_store.rs +++ b/crates/rooch-indexer/src/store/sqlite_store.rs @@ -3,7 +3,6 @@ use anyhow::Result; use diesel::RunQueryDsl; -use tracing::info; use crate::errors::{Context, IndexerError}; @@ -63,7 +62,6 @@ impl SqliteIndexerStore { .map_err(IndexerError::from) .context("Failed to write events to SQLiteDB")?; - info!("Persisted events: {:?}", events); Ok(()) } } diff --git a/crates/rooch-indexer/src/types.rs b/crates/rooch-indexer/src/types.rs index 2a80e7a693..40a7b38f12 100644 --- a/crates/rooch-indexer/src/types.rs +++ b/crates/rooch-indexer/src/types.rs @@ -5,7 +5,6 @@ use crate::errors::IndexerError; use anyhow::Result; use move_core_types::account_address::AccountAddress; use move_core_types::language_storage::StructTag; -use move_core_types::vm_status::KeptVMStatus; use moveos_types::h256::H256; use moveos_types::moveos_std::event::Event; use moveos_types::moveos_std::object::ObjectID; @@ -27,8 +26,9 @@ pub struct IndexedTransaction { pub transaction_type: TransactionType, pub sequence_number: u64, pub multichain_id: MultiChainID, - //TODO transform to hex - pub multichain_raw_address: String, + pub multichain_address: String, + // the orginal address str + pub multichain_original_address: String, /// the account address of sender who send the transaction pub sender: AccountAddress, pub action: MoveAction, @@ -44,8 +44,7 @@ pub struct IndexedTransaction { /// the amount of gas used. pub gas_used: u64, /// the vm status. - pub status: KeptVMStatus, - + pub status: String, /// The tx order signature, pub tx_order_auth_validator_id: u64, pub tx_order_authenticator_payload: Vec, @@ -63,6 +62,8 @@ impl IndexedTransaction { let move_action = MoveAction::from(moveos_tx.action); let action_raw = move_action.encode()?; let transaction_authenticator_info = transaction.authenticator_info()?; + let status = serde_json::to_string(&execution_info.status)?; + let indexed_transaction = IndexedTransaction { tx_hash: transaction.tx_hash(), /// The tx order of this transaction. @@ -71,8 +72,8 @@ impl IndexedTransaction { transaction_type: transaction.transaction_type(), sequence_number: moveos_tx.ctx.sequence_number, multichain_id: transaction.multi_chain_id(), - //TODO transform to hex - multichain_raw_address: transaction.sender().to_string(), + multichain_address: transaction.sender().to_string(), + multichain_original_address: transaction.original_address_str(), /// the account address of sender who send the transaction sender: moveos_tx.ctx.sender, action: move_action.clone(), @@ -90,7 +91,7 @@ impl IndexedTransaction { /// the amount of gas used. gas_used: execution_info.gas_used, /// the vm status. - status: execution_info.status, + status, /// The tx order signature, tx_order_auth_validator_id: sequence_info.tx_order_signature.auth_validator_id, diff --git a/crates/rooch-open-rpc-spec/schemas/openrpc.json b/crates/rooch-open-rpc-spec/schemas/openrpc.json index cb0acd33cc..d700e5aa3e 100644 --- a/crates/rooch-open-rpc-spec/schemas/openrpc.json +++ b/crates/rooch-open-rpc-spec/schemas/openrpc.json @@ -331,6 +331,44 @@ } } }, + { + "name": "rooch_queryTransactions", + "description": "Query the transactions indexer by transaction filter", + "params": [ + { + "name": "filter", + "required": true, + "schema": { + "$ref": "#/components/schemas/TransactionFilterView" + } + }, + { + "name": "cursor", + "schema": { + "$ref": "#/components/schemas/u64" + } + }, + { + "name": "limit", + "schema": { + "$ref": "#/components/schemas/usize" + } + }, + { + "name": "descending_order", + "schema": { + "type": "boolean" + } + } + ], + "result": { + "name": "TransactionWithInfoPageView", + "required": true, + "schema": { + "$ref": "#/components/schemas/PageView_for_TransactionWithInfoView_and_uint64" + } + } + }, { "name": "rooch_sendRawTransaction", "description": "Send the signed transaction in bcs hex format This method does not block waiting for the transaction to be executed.", @@ -521,10 +559,10 @@ "description": "Query by event type.", "type": "object", "required": [ - "eventType" + "event_type" ], "properties": { - "eventType": { + "event_type": { "$ref": "#/components/schemas/move_core_types::language_storage::StructTag" } }, @@ -547,10 +585,10 @@ "description": "Return events emitted by the given transaction hash.", "type": "object", "required": [ - "txHash" + "tx_hash" ], "properties": { - "txHash": { + "tx_hash": { "$ref": "#/components/schemas/primitive_types::H256" } }, @@ -560,10 +598,10 @@ "description": "Return events emitted in [start_time, end_time) interval", "type": "object", "required": [ - "timeRange" + "time_range" ], "properties": { - "timeRange": { + "time_range": { "type": "object", "required": [ "end_time", @@ -591,10 +629,10 @@ "description": "Return events emitted in [from_order, to_order) interval", "type": "object", "required": [ - "txOrderRange" + "tx_order_range" ], "properties": { - "txOrderRange": { + "tx_order_range": { "type": "object", "required": [ "from_order", @@ -1373,6 +1411,114 @@ } } }, + "TransactionFilterView": { + "oneOf": [ + { + "description": "Query by sender address.", + "type": "object", + "required": [ + "sender" + ], + "properties": { + "sender": { + "$ref": "#/components/schemas/move_core_types::account_address::AccountAddress" + } + }, + "additionalProperties": false + }, + { + "description": "Query by multi chain original address.", + "type": "object", + "required": [ + "original_address" + ], + "properties": { + "original_address": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "description": "Query by the given transaction hash.", + "type": "object", + "required": [ + "tx_hashes" + ], + "properties": { + "tx_hashes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/primitive_types::H256" + } + } + }, + "additionalProperties": false + }, + { + "description": "Return transactions in [start_time, end_time) interval", + "type": "object", + "required": [ + "time_range" + ], + "properties": { + "time_range": { + "type": "object", + "required": [ + "end_time", + "start_time" + ], + "properties": { + "end_time": { + "description": "right endpoint of time interval, milliseconds since block, exclusive", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "start_time": { + "description": "left endpoint of time interval, milliseconds since block, inclusive", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "description": "Return events emitted in [from_order, to_order) interval", + "type": "object", + "required": [ + "tx_order_range" + ], + "properties": { + "tx_order_range": { + "type": "object", + "required": [ + "from_order", + "to_order" + ], + "properties": { + "from_order": { + "description": "left endpoint of transaction order, inclusive", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "to_order": { + "description": "right endpoint of transaction order, exclusive", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + } + ] + }, "TransactionOutputView": { "type": "object", "required": [ diff --git a/crates/rooch-rpc-api/src/api/rooch_api.rs b/crates/rooch-rpc-api/src/api/rooch_api.rs index babb2d1f4f..27729746bf 100644 --- a/crates/rooch-rpc-api/src/api/rooch_api.rs +++ b/crates/rooch-rpc-api/src/api/rooch_api.rs @@ -3,7 +3,7 @@ use crate::jsonrpc_types::account_view::BalanceInfoView; use crate::jsonrpc_types::event_view::EventFilterView; -use crate::jsonrpc_types::transaction_view::TransactionWithInfoView; +use crate::jsonrpc_types::transaction_view::{TransactionFilterView, TransactionWithInfoView}; use crate::jsonrpc_types::{ AccessPathView, AccountAddressView, AnnotatedFunctionResultView, BalanceInfoPageView, BytesView, EventOptions, EventPageView, ExecuteTransactionResponseView, FunctionCallView, @@ -103,6 +103,17 @@ pub trait RoochAPI { limit: Option>, ) -> RpcResult; + /// Query the transactions indexer by transaction filter + #[method(name = "queryTransactions")] + async fn query_transactions( + &self, + filter: TransactionFilterView, + // exclusive cursor if `Some`, otherwise start from the beginning + cursor: Option>, + limit: Option>, + descending_order: Option, + ) -> RpcResult; + /// Query the events indexer by event filter #[method(name = "queryEvents")] async fn query_events( diff --git a/crates/rooch-rpc-api/src/jsonrpc_types/event_view.rs b/crates/rooch-rpc-api/src/jsonrpc_types/event_view.rs index 6a48d5aa92..a4b89804c3 100644 --- a/crates/rooch-rpc-api/src/jsonrpc_types/event_view.rs +++ b/crates/rooch-rpc-api/src/jsonrpc_types/event_view.rs @@ -102,7 +102,7 @@ impl From for IndexerEventView { } #[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] -#[serde(rename_all = "camelCase")] +#[serde(rename_all = "snake_case")] pub enum EventFilterView { /// Query by event type. EventType(StructTagView), diff --git a/crates/rooch-rpc-api/src/jsonrpc_types/transaction_view.rs b/crates/rooch-rpc-api/src/jsonrpc_types/transaction_view.rs index 31bdbe1c73..f78ba78f0e 100644 --- a/crates/rooch-rpc-api/src/jsonrpc_types/transaction_view.rs +++ b/crates/rooch-rpc-api/src/jsonrpc_types/transaction_view.rs @@ -2,8 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use crate::jsonrpc_types::{ - TransactionExecutionInfoView, TransactionSequenceInfoView, TransactionView, + AccountAddressView, H256View, TransactionExecutionInfoView, TransactionSequenceInfoView, + TransactionView, }; +use rooch_types::indexer::transaction_filter::TransactionFilter; use rooch_types::transaction::TransactionWithInfo; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -24,3 +26,55 @@ impl From for TransactionWithInfoView { } } } + +#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum TransactionFilterView { + /// Query by sender address. + Sender(AccountAddressView), + /// Query by multi chain original address. + OriginalAddress(String), + /// Query by the given transaction hash. + TxHashes(Vec), + /// Return transactions in [start_time, end_time) interval + TimeRange { + /// left endpoint of time interval, milliseconds since block, inclusive + start_time: u64, + /// right endpoint of time interval, milliseconds since block, exclusive + end_time: u64, + }, + /// Return events emitted in [from_order, to_order) interval + // #[serde(rename_all = "camelCase")] + TxOrderRange { + /// left endpoint of transaction order, inclusive + from_order: u64, + /// right endpoint of transaction order, exclusive + to_order: u64, + }, +} + +impl From for TransactionFilter { + fn from(event_filter: TransactionFilterView) -> Self { + match event_filter { + TransactionFilterView::Sender(address) => Self::Sender(address.into()), + TransactionFilterView::OriginalAddress(address) => Self::OriginalAddress(address), + TransactionFilterView::TxHashes(tx_hashes) => { + Self::TxHashes(tx_hashes.into_iter().map(Into::into).collect()) + } + TransactionFilterView::TimeRange { + start_time, + end_time, + } => Self::TimeRange { + start_time, + end_time, + }, + TransactionFilterView::TxOrderRange { + from_order, + to_order, + } => Self::TxOrderRange { + from_order, + to_order, + }, + } + } +} diff --git a/crates/rooch-rpc-server/src/server/rooch_server.rs b/crates/rooch-rpc-server/src/server/rooch_server.rs index 0b11767e4d..b40978a0f8 100644 --- a/crates/rooch-rpc-server/src/server/rooch_server.rs +++ b/crates/rooch-rpc-server/src/server/rooch_server.rs @@ -9,6 +9,7 @@ use jsonrpsee::{ }; use moveos_types::h256::H256; use rooch_rpc_api::jsonrpc_types::event_view::{EventFilterView, EventView, IndexerEventView}; +use rooch_rpc_api::jsonrpc_types::transaction_view::TransactionFilterView; use rooch_rpc_api::jsonrpc_types::{ account_view::BalanceInfoView, IndexerEventPageView, StateOptions, }; @@ -333,6 +334,43 @@ impl RoochAPIServer for RoochServer { }) } + async fn query_transactions( + &self, + filter: TransactionFilterView, + // exclusive cursor if `Some`, otherwise start from the beginning + cursor: Option>, + limit: Option>, + descending_order: Option, + ) -> RpcResult { + let limit_of = min( + limit.map(Into::into).unwrap_or(DEFAULT_RESULT_LIMIT_USIZE), + MAX_RESULT_LIMIT_USIZE, + ); + let cursor = cursor.map(|v| v.0); + let descending_order = descending_order.unwrap_or(true); + + let mut data = self + .rpc_service + .query_transactions(filter.into(), cursor, limit_of + 1, descending_order) + .await?; + + let has_next_page = data.len() > limit_of; + data.truncate(limit_of); + let next_cursor = data + .last() + .cloned() + .map_or(cursor, |t| Some(t.sequence_info.tx_order)); + + Ok(TransactionWithInfoPageView { + data: data + .into_iter() + .map(TransactionWithInfoView::from) + .collect::>(), + next_cursor, + has_next_page, + }) + } + async fn query_events( &self, filter: EventFilterView, diff --git a/crates/rooch-rpc-server/src/service/rpc_service.rs b/crates/rooch-rpc-server/src/service/rpc_service.rs index 001830b7b9..0eb6515c79 100644 --- a/crates/rooch-rpc-server/src/service/rpc_service.rs +++ b/crates/rooch-rpc-server/src/service/rpc_service.rs @@ -19,10 +19,11 @@ use rooch_sequencer::proxy::SequencerProxy; use rooch_types::account::Account; use rooch_types::address::{MultiChainAddress, RoochAddress}; use rooch_types::indexer::event_filter::{EventFilter, IndexerEvent, IndexerEventID}; +use rooch_types::indexer::transaction_filter::TransactionFilter; use rooch_types::sequencer::SequencerOrder; use rooch_types::transaction::rooch::RoochTransaction; -use rooch_types::transaction::TypedTransaction; use rooch_types::transaction::{TransactionSequenceInfo, TransactionSequenceInfoMapping}; +use rooch_types::transaction::{TransactionWithInfo, TypedTransaction}; /// RpcService is the implementation of the RPC service. /// It is the glue between the RPC server(EthAPIServer,RoochApiServer) and the rooch's actors. @@ -241,6 +242,21 @@ impl RpcService { Ok(resp) } + pub async fn query_transactions( + &self, + filter: TransactionFilter, + // exclusive cursor if `Some`, otherwise start from the beginning + cursor: Option, + limit: usize, + descending_order: bool, + ) -> Result> { + let resp = self + .indexer + .query_transactions(filter, cursor, limit, descending_order) + .await?; + Ok(resp) + } + pub async fn query_events( &self, filter: EventFilter, diff --git a/crates/rooch-types/src/indexer/event_filter.rs b/crates/rooch-types/src/indexer/event_filter.rs index b1c00a1370..0ea0d31991 100644 --- a/crates/rooch-types/src/indexer/event_filter.rs +++ b/crates/rooch-types/src/indexer/event_filter.rs @@ -74,7 +74,7 @@ pub enum EventFilter { /// right endpoint of time interval, milliseconds since epoch, exclusive end_time: u64, }, - /// Return events emitted in [from_tx_order, to_tx_order) interval + /// Return events emitted in [from_order, to_order) interval TxOrderRange { /// left endpoint of transaction order, inclusive from_order: u64, diff --git a/crates/rooch-types/src/indexer/mod.rs b/crates/rooch-types/src/indexer/mod.rs index 35ed96f849..4f847faac9 100644 --- a/crates/rooch-types/src/indexer/mod.rs +++ b/crates/rooch-types/src/indexer/mod.rs @@ -1,6 +1,7 @@ // Copyright (c) RoochNetwork // SPDX-License-Identifier: Apache-2.0 pub mod event_filter; +pub mod transaction_filter; pub trait Filter { fn matches(&self, item: &T) -> bool; diff --git a/crates/rooch-types/src/indexer/transaction_filter.rs b/crates/rooch-types/src/indexer/transaction_filter.rs new file mode 100644 index 0000000000..2218eff56d --- /dev/null +++ b/crates/rooch-types/src/indexer/transaction_filter.rs @@ -0,0 +1,31 @@ +// Copyright (c) RoochNetwork +// SPDX-License-Identifier: Apache-2.0 + +use move_core_types::account_address::AccountAddress; +use moveos_types::h256::H256; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum TransactionFilter { + /// Query by sender address. + Sender(AccountAddress), + /// Query by multi chain original address. + OriginalAddress(String), + /// Query by the transaction hash list. + TxHashes(Vec), + /// Return transactions in [start_time, end_time) interval + TimeRange { + /// left endpoint of time interval, milliseconds since epoch, inclusive + start_time: u64, + /// right endpoint of time interval, milliseconds since epoch, exclusive + end_time: u64, + }, + /// Return transactions in [from_order, to_order) interval + TxOrderRange { + /// left endpoint of transaction order, inclusive + from_order: u64, + /// right endpoint of transaction order, exclusive + to_order: u64, + }, +} diff --git a/crates/rooch-types/src/transaction/ethereum.rs b/crates/rooch-types/src/transaction/ethereum.rs index 114afdadd5..00b6f21cc5 100644 --- a/crates/rooch-types/src/transaction/ethereum.rs +++ b/crates/rooch-types/src/transaction/ethereum.rs @@ -360,6 +360,10 @@ impl AbstractTransaction for EthereumTransaction { EthereumAddress(self.0.from).into() } + fn original_address_str(&self) -> String { + self.0.from.to_string() + } + fn authenticator_info(&self) -> Result { let chain_id = self.0.chain_id.ok_or(RoochError::InvalidChainID)?.as_u64(); let authenticator = Authenticator::new( diff --git a/crates/rooch-types/src/transaction/mod.rs b/crates/rooch-types/src/transaction/mod.rs index 309809e6c8..055b27d331 100644 --- a/crates/rooch-types/src/transaction/mod.rs +++ b/crates/rooch-types/src/transaction/mod.rs @@ -4,13 +4,14 @@ use self::{authenticator::Authenticator, ethereum::EthereumTransaction, rooch::RoochTransaction}; use crate::address::MultiChainAddress; use crate::multichain_id::{MultiChainID, ETHER, ROOCH}; -use anyhow::Result; +use anyhow::{format_err, Result}; use move_core_types::account_address::AccountAddress; use moveos_types::transaction::TransactionExecutionInfo; use moveos_types::{h256::H256, transaction::MoveOSTransaction}; use serde::{Deserialize, Serialize}; use std::fmt; use std::fmt::{Display, Formatter}; +use std::str::FromStr; pub mod authenticator; pub mod ethereum; @@ -37,6 +38,18 @@ impl Display for TransactionType { } } +impl FromStr for TransactionType { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + match s { + "Rooch" => Ok(TransactionType::Rooch), + "Ethereum" => Ok(TransactionType::Ethereum), + s => Err(format_err!("Unknown transaction type: {}", s)), + } + } +} + #[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] pub struct RawTransaction { pub transaction_type: TransactionType, @@ -78,6 +91,8 @@ pub trait AbstractTransaction { fn sender(&self) -> MultiChainAddress; + fn original_address_str(&self) -> String; + fn tx_hash(&self) -> H256; fn authenticator_info(&self) -> Result; @@ -167,6 +182,13 @@ impl AbstractTransaction for TypedTransaction { } } + fn original_address_str(&self) -> String { + match self { + TypedTransaction::Rooch(tx) => tx.original_address_str(), + TypedTransaction::Ethereum(tx) => tx.original_address_str(), + } + } + fn multi_chain_id(&self) -> MultiChainID { match self { TypedTransaction::Rooch(_tx) => MultiChainID::from(ROOCH), diff --git a/crates/rooch-types/src/transaction/rooch.rs b/crates/rooch-types/src/transaction/rooch.rs index 7ffa9cbb8c..0916786d6e 100644 --- a/crates/rooch-types/src/transaction/rooch.rs +++ b/crates/rooch-types/src/transaction/rooch.rs @@ -205,6 +205,10 @@ impl AbstractTransaction for RoochTransaction { self.sender().into() } + fn original_address_str(&self) -> String { + self.data.sender.to_string() + } + fn multi_chain_id(&self) -> MultiChainID { MultiChainID::from(ROOCH) } diff --git a/crates/testsuite/features/cmd.feature b/crates/testsuite/features/cmd.feature index fb8bbc669a..65678056a7 100644 --- a/crates/testsuite/features/cmd.feature +++ b/crates/testsuite/features/cmd.feature @@ -106,7 +106,31 @@ Feature: Rooch CLI integration tests Then assert: "{{$.event[-1].has_next_page}} == false" Then stop the server - @serial + @serial + Scenario: indexer + Given a server for indexer + Then cmd: "move publish -p ../../examples/event --named-addresses rooch_examples=default" + Then assert: "{{$.move[-1].execution_info.status.type}} == executed" + Then cmd: "move run --function default::event_test::emit_event --args 10u64" + Then assert: "{{$.move[-1].execution_info.status.type}} == executed" + Then cmd: "move run --function default::event_test::emit_event --args 11u64" + Then assert: "{{$.move[-1].execution_info.status.type}} == executed" + + Then cmd: "rpc request --method rooch_queryTransactions --params '[{"tx_order_range":{"from_order":0,"to_order":2}}, null, "1", true']" + Then assert: "{{$.rpc[-1].data[0].sequence_info.tx_order}} == 1" + Then assert: "{{$.rpc[-1].next_cursor}} == 1" + Then assert: "{{$.rpc[-1].has_next_page}} == true" + Then cmd: "rpc request --method rooch_queryTransactions --params '[{"tx_order_range":{"from_order":0,"to_order":2}}, "1", "1", true']" + Then assert: "{{$.rpc[-1].data[0].sequence_info.tx_order}} == 0" + Then assert: "{{$.rpc[-1].next_cursor}} == 0" + Then assert: "{{$.rpc[-1].has_next_page}} == false" + Then cmd: "rpc request --method rooch_queryEvents --params '[{"tx_order_range":{"from_order":0, "to_order":2}}, null, "10", true']" + Then assert: "{{$.rpc[-1].data[0].indexer_event_id.tx_order}} == 1" + Then assert: "{{$.rpc[-1].next_cursor.tx_order}} == 0" + Then assert: "{{$.rpc[-1].has_next_page}} == false" + Then stop the server + + @serial Scenario: kv_store example Given a server for kv_store Then cmd: "move publish -p ../../examples/kv_store --named-addresses rooch_examples=default"