From 9d85ea234e7f4c93ab54bd1b3427e22c98ac5a34 Mon Sep 17 00:00:00 2001 From: Sergio Chouhy Date: Tue, 22 Jul 2025 10:23:52 -0300 Subject: [PATCH] add get transaction by hash rpc method --- common/src/rpc_primitives/requests.rs | 11 ++++ .../src/sequencer_store/block_store.rs | 45 +++++++++++++-- sequencer_rpc/src/process.rs | 55 +++++++++++++++++-- 3 files changed, 101 insertions(+), 10 deletions(-) diff --git a/common/src/rpc_primitives/requests.rs b/common/src/rpc_primitives/requests.rs index e17c786..48e41ea 100644 --- a/common/src/rpc_primitives/requests.rs +++ b/common/src/rpc_primitives/requests.rs @@ -39,6 +39,11 @@ pub struct GetAccountBalanceRequest { pub address: String, } +#[derive(Serialize, Deserialize, Debug)] +pub struct GetTransactionByHashRequest { + pub hash: String, +} + parse_request!(HelloRequest); parse_request!(RegisterAccountRequest); parse_request!(SendTxRequest); @@ -46,6 +51,7 @@ parse_request!(GetBlockDataRequest); parse_request!(GetGenesisIdRequest); parse_request!(GetLastBlockRequest); parse_request!(GetAccountBalanceRequest); +parse_request!(GetTransactionByHashRequest); #[derive(Serialize, Deserialize, Debug)] pub struct HelloResponse { @@ -81,3 +87,8 @@ pub struct GetLastBlockResponse { pub struct GetAccountBalanceResponse { pub balance: u64, } + +#[derive(Serialize, Deserialize, Debug)] +pub struct GetTransactionByHashResponse { + pub transaction: Option, +} diff --git a/sequencer_core/src/sequencer_store/block_store.rs b/sequencer_core/src/sequencer_store/block_store.rs index 8c556a3..e4611c5 100644 --- a/sequencer_core/src/sequencer_store/block_store.rs +++ b/sequencer_core/src/sequencer_store/block_store.rs @@ -1,11 +1,12 @@ -use std::path::Path; +use std::{collections::HashMap, path::Path}; use anyhow::Result; -use common::block::Block; +use common::{block::Block, merkle_tree_public::TreeHashType, transaction::Transaction}; use storage::RocksDBIO; pub struct SequecerBlockStore { dbio: RocksDBIO, + tx_hash_to_block_map: HashMap, pub genesis_id: u64, } @@ -15,11 +16,21 @@ impl SequecerBlockStore { /// /// ATTENTION: Will overwrite genesis block. pub fn open_db_with_genesis(location: &Path, genesis_block: Option) -> Result { + let tx_hash_to_block_map = if let Some(block) = &genesis_block { + block_to_transactions_map(block) + } else { + HashMap::new() + }; + let dbio = RocksDBIO::new(location, genesis_block)?; let genesis_id = dbio.get_meta_first_block_in_db()?; - Ok(Self { dbio, genesis_id }) + Ok(Self { + dbio, + genesis_id, + tx_hash_to_block_map, + }) } ///Reopening existing database @@ -31,7 +42,31 @@ impl SequecerBlockStore { Ok(self.dbio.get_block(id)?) } - pub fn put_block_at_id(&self, block: Block) -> Result<()> { - Ok(self.dbio.put_block(block, false)?) + pub fn put_block_at_id(&mut self, block: Block) -> Result<()> { + let new_transactions_map = block_to_transactions_map(&block); + self.dbio.put_block(block, false)?; + self.tx_hash_to_block_map.extend(new_transactions_map); + Ok(()) + } + + pub fn get_transaction_by_hash(&self, hash: TreeHashType) -> Option { + let block_id = self.tx_hash_to_block_map.get(&hash); + let block = block_id.map(|&id| self.get_block_at_id(id)); + if let Some(Ok(block)) = block { + for transaction in block.transactions.into_iter() { + if transaction.hash() == hash { + return Some(transaction); + } + } + } + None } } + +fn block_to_transactions_map(block: &Block) -> HashMap { + block + .transactions + .iter() + .map(|transaction| (transaction.hash(), block.block_id)) + .collect() +} diff --git a/sequencer_rpc/src/process.rs b/sequencer_rpc/src/process.rs index f8bc880..3e68598 100644 --- a/sequencer_rpc/src/process.rs +++ b/sequencer_rpc/src/process.rs @@ -1,11 +1,17 @@ use actix_web::Error as HttpError; use serde_json::Value; -use common::rpc_primitives::{ - errors::{RpcError, RpcParseError}, - message::{Message, Request}, - parser::RpcRequest, - requests::{GetAccountBalanceRequest, GetAccountBalanceResponse}, +use common::{ + merkle_tree_public::TreeHashType, + rpc_primitives::{ + errors::{RpcError, RpcParseError}, + message::{Message, Request}, + parser::RpcRequest, + requests::{ + GetAccountBalanceRequest, GetAccountBalanceResponse, GetTransactionByHashRequest, + GetTransactionByHashResponse, + }, + }, }; use common::rpc_primitives::requests::{ @@ -23,6 +29,7 @@ pub const GET_BLOCK: &str = "get_block"; pub const GET_GENESIS: &str = "get_genesis"; pub const GET_LAST_BLOCK: &str = "get_last_block"; pub const GET_ACCOUNT_BALANCE: &str = "get_account_balance"; +pub const GET_TRANSACTION_BY_HASH: &str = "get_transaction_by_hash"; pub const HELLO_FROM_SEQUENCER: &str = "HELLO_FROM_SEQUENCER"; @@ -156,6 +163,21 @@ impl JsonHandler { respond(helperstruct) } + async fn process_get_transaction_by_hash(&self, request: Request) -> Result { + let get_transaction_req = GetTransactionByHashRequest::parse(Some(request.params))?; + let bytes: Vec = hex::decode(get_transaction_req.hash) + .map_err(|_| RpcParseError("invalid hash".to_string()))?; + let hash: TreeHashType = bytes + .try_into() + .map_err(|_| RpcParseError("invalid hash".to_string()))?; + + let transaction = { + let state = self.sequencer_state.lock().await; + state.store.block_store.get_transaction_by_hash(hash) + }; + let helperstruct = GetTransactionByHashResponse { transaction }; + respond(helperstruct) + } pub async fn process_request_internal(&self, request: Request) -> Result { match request.method.as_ref() { @@ -166,6 +188,7 @@ impl JsonHandler { GET_GENESIS => self.process_get_genesis(request).await, GET_LAST_BLOCK => self.process_get_last_block(request).await, GET_ACCOUNT_BALANCE => self.process_get_account_balance(request).await, + GET_TRANSACTION_BY_HASH => self.process_get_transaction_by_hash(request).await, _ => Err(RpcErr(RpcError::method_not_found(request.method))), } } @@ -315,4 +338,26 @@ mod tests { assert_eq!(response, expected_response); } + + #[actix_web::test] + async fn test_get_transaction_by_hash_for_non_existent_hash() { + let json_handler = json_handler_for_tests(); + let request = serde_json::json!({ + "jsonrpc": "2.0", + "method": "get_transaction_by_hash", + "params": { "hash": "cafe".repeat(16) }, + "id": 1 + }); + let expected_response = serde_json::json!({ + "id": 1, + "jsonrpc": "2.0", + "result": { + "transaction": Value::Null + } + }); + + let response = call_rpc_handler_with_json(json_handler, request).await; + + assert_eq!(response, expected_response); + } }