From 7e99a98643ed4d1d32de2e7d6e7bb41502d43dd2 Mon Sep 17 00:00:00 2001 From: viraladmin <00purple@gmail.com> Date: Sun, 14 Jun 2026 15:26:18 -0600 Subject: [PATCH] standalone tools fix for command 47 --- src/rpc/command_maps.rs | 1 + src/rpc/commands/address_history.rs | 34 ++++++++++++++++ src/rpc/commands/transaction_by_txid.rs | 39 ++++++++++--------- src/rpc/server/rpc_command_loop.rs | 24 ++++++++++++ .../connections/sending_request.rs | 22 +++++++++-- 5 files changed, 98 insertions(+), 22 deletions(-) diff --git a/src/rpc/command_maps.rs b/src/rpc/command_maps.rs index 963b201..300c039 100644 --- a/src/rpc/command_maps.rs +++ b/src/rpc/command_maps.rs @@ -57,6 +57,7 @@ pub const RPC_WALLET_REGISTRATION_STATUS: u8 = 43; pub const RPC_ADDRESS_HISTORY: u8 = 44; pub const RPC_VANITY_OWNER_LOOKUP: u8 = 45; pub const RPC_LATEST_ADDRESS_HISTORY: u8 = 46; +pub const RPC_LATEST_ADDRESS_TRANSACTIONS: u8 = 47; pub const RPC_REPLY: u8 = 255; pub const MAX_RPC_REPLY_BYTES: usize = 64 * 1024 * 1024; diff --git a/src/rpc/commands/address_history.rs b/src/rpc/commands/address_history.rs index 2d2b109..7fee745 100644 --- a/src/rpc/commands/address_history.rs +++ b/src/rpc/commands/address_history.rs @@ -1,5 +1,6 @@ use crate::records::memory::mempool::latest_pending_txids_by_address; use crate::records::record_chain::wallet_tx_index::WALLET_TX_INDEX_TREE; +use crate::rpc::commands::transaction_by_txid::transaction_bytes_with_block; use crate::rpc::responses::RpcResponse; use crate::sled::Db; use crate::wallets::structures::Wallet; @@ -151,3 +152,36 @@ pub async fn latest(address_bytes: Vec, limit: u32, db: &Db) -> RpcResponse RpcResponse::Binary(txids) } + +pub async fn latest_transactions(address_bytes: Vec, limit: u32, db: &Db) -> RpcResponse { + let RpcResponse::Binary(txid_bytes) = latest(address_bytes, limit, db).await; + if txid_bytes.starts_with(b"error:") { + return RpcResponse::Binary(txid_bytes); + } + + let mut entries = Vec::new(); + for txid in txid_bytes.chunks_exact(TXID_LENGTH) { + match transaction_bytes_with_block(db, txid).await { + Ok((block_height, transaction_bytes)) => { + entries.push((txid.to_vec(), block_height, transaction_bytes)); + } + Err(err) => { + return RpcResponse::Binary(err.into_bytes()); + } + } + } + + let mut response = Vec::new(); + response.extend_from_slice(&(entries.len() as u32).to_le_bytes()); + for (txid, block_height, transaction_bytes) in entries { + let Ok(tx_len) = u32::try_from(transaction_bytes.len()) else { + return RpcResponse::Binary(b"error: Transaction payload too large".to_vec()); + }; + response.extend_from_slice(&txid); + response.extend_from_slice(&block_height.to_le_bytes()); + response.extend_from_slice(&tx_len.to_le_bytes()); + response.extend_from_slice(&transaction_bytes); + } + + RpcResponse::Binary(response) +} diff --git a/src/rpc/commands/transaction_by_txid.rs b/src/rpc/commands/transaction_by_txid.rs index 8382fea..55df69e 100644 --- a/src/rpc/commands/transaction_by_txid.rs +++ b/src/rpc/commands/transaction_by_txid.rs @@ -15,7 +15,7 @@ const HEADER_SIZE: u64 = VRF_BLOCK_BYTES as u64; pub async fn request_transaction_by_txid(db: &Db, txid: Vec) -> RpcResponse { // Resolve the saved transaction bytes directly from the txid lookup // tree and the referenced block file. - match lookup_transaction_location(db, txid).await { + match lookup_transaction_location(db, &txid).await { Ok((_block, _position, block_filename)) => { let bytes = calculate_offset(&block_filename, _position).await; match bytes { @@ -33,36 +33,37 @@ pub async fn request_transaction_by_txid(db: &Db, txid: Vec) -> RpcResponse pub async fn request_transaction_by_txid_with_block(db: &Db, txid: Vec) -> RpcResponse { // Some callers need the block number alongside the raw transaction // bytes, so this variant prefixes the payload with the block height. - match lookup_transaction_location(db, txid.clone()).await { + match transaction_bytes_with_block(db, &txid).await { + Ok((block, vec)) => { + let mut response = Vec::with_capacity(4 + vec.len()); + response.extend_from_slice(&block.to_le_bytes()); + response.extend_from_slice(&vec); + RpcResponse::Binary(response) + } + Err(msg) => RpcResponse::Binary(msg.into_bytes()), + } +} + +pub async fn transaction_bytes_with_block(db: &Db, txid: &[u8]) -> Result<(u32, Vec), String> { + match lookup_transaction_location(db, txid).await { Ok((block, position, block_filename)) => { let bytes = calculate_offset(&block_filename, position).await; match bytes { - Some(vec) => { - let mut response = Vec::with_capacity(4 + vec.len()); - response.extend_from_slice(&(block as u32).to_le_bytes()); - response.extend_from_slice(&vec); - RpcResponse::Binary(response) - } - None => { - let msg = "error: Error parsing block".to_string().as_bytes().to_vec(); - RpcResponse::Binary(msg) - } + Some(vec) => Ok((block as u32, vec)), + None => Err("error: Error parsing block".to_string()), } } Err(msg) => { - if let Some(bytes) = pending_transaction_by_txid(&txid).await { - let mut response = Vec::with_capacity(4 + bytes.len()); - response.extend_from_slice(&0u32.to_le_bytes()); - response.extend_from_slice(&bytes); - RpcResponse::Binary(response) + if let Some(bytes) = pending_transaction_by_txid(txid).await { + Ok((0, bytes)) } else { - RpcResponse::Binary(msg.into_bytes()) + Err(msg) } } } } -async fn lookup_transaction_location(db: &Db, txid: Vec) -> Result<(u64, u32, String), String> { +async fn lookup_transaction_location(db: &Db, txid: &[u8]) -> Result<(u64, u32, String), String> { // The txid tree stores `block:index`, which is enough to locate the // transaction inside the saved block file on disk. let tree = db.open_tree("txid").unwrap(); diff --git a/src/rpc/server/rpc_command_loop.rs b/src/rpc/server/rpc_command_loop.rs index 696d24e..082b668 100644 --- a/src/rpc/server/rpc_command_loop.rs +++ b/src/rpc/server/rpc_command_loop.rs @@ -906,6 +906,30 @@ pub async fn start_loop( .send(&stream_locked, Some(&connections_key), uid) .await; } + 47 => { + // request latest transaction records for a wallet address in one response + let (uid, _) = read_bytes_from_stream::read_uid_from_stream( + &connections_key, + stream_locked.clone(), + ) + .await?; + let short_address = read_bytes_from_stream::read_short_address_from_stream( + &connections_key, + stream_locked.clone(), + ) + .await?; + let limit = read_bytes_from_stream::read_u32_from_stream( + &connections_key, + stream_locked.clone(), + ) + .await?; + + let result = + commands::address_history::latest_transactions(short_address, limit, &db).await; + result + .send(&stream_locked, Some(&connections_key), uid) + .await; + } 255 => { commands::route_reply::route_reply( &connections_key, diff --git a/src/standalone_tools/connections/sending_request.rs b/src/standalone_tools/connections/sending_request.rs index 5f19695..1e7e071 100644 --- a/src/standalone_tools/connections/sending_request.rs +++ b/src/standalone_tools/connections/sending_request.rs @@ -5,9 +5,9 @@ use crate::records::memory::response_channels::Byte3; use crate::rpc::command_maps::{ MAX_RPC_REPLY_BYTES, RPC_ADDRESS_HISTORY, RPC_ADDRESS_TOTAL_BALANCE, RPC_BLOCK_BY_HASH, RPC_BLOCK_BY_HEIGHT, RPC_BLOCK_HEIGHT, RPC_BLOCK_IP, RPC_CONTRACT_BY_ADDRESS, RPC_DIFFICULTY, - RPC_LARGEST_TX_FEE, RPC_LATEST_ADDRESS_HISTORY, RPC_LOAN_CONTRACT, RPC_MEMPOOL_TX_BY_ADDRESS, - RPC_MEMPOOL_TX_BY_SIGNATURE, RPC_MEMPOOL_TX_COUNT, RPC_NETWORK_INFO, RPC_NFT_DETAILS, - RPC_NFT_LIST, RPC_REGISTER_WALLET, RPC_TIME, RPC_TOKEN_DETAILS, RPC_TOKEN_LIST, + RPC_LARGEST_TX_FEE, RPC_LATEST_ADDRESS_HISTORY, RPC_LATEST_ADDRESS_TRANSACTIONS, RPC_LOAN_CONTRACT, + RPC_MEMPOOL_TX_BY_ADDRESS, RPC_MEMPOOL_TX_BY_SIGNATURE, RPC_MEMPOOL_TX_COUNT, RPC_NETWORK_INFO, + RPC_NFT_DETAILS, RPC_NFT_LIST, RPC_REGISTER_WALLET, RPC_TIME, RPC_TOKEN_DETAILS, RPC_TOKEN_LIST, RPC_TORRENT_BY_HEIGHT, RPC_TOTAL_CONFIRMED_TX, RPC_TRANSACTION_BY_TXID, RPC_UNBLOCK_IP, RPC_VANITY_LOOKUP, RPC_VANITY_OWNER_LOOKUP, RPC_WALLET_REGISTRATION_STATUS, }; @@ -541,6 +541,22 @@ async fn build_request_bytes( bin_msg.extend_from_slice(&address_bytes); bin_msg.extend_from_slice(&limit.to_le_bytes()); } + 47 => { + // NEW: latest address transaction bytes (RPC_LATEST_ADDRESS_TRANSACTIONS) + let command_number: u8 = RPC_LATEST_ADDRESS_TRANSACTIONS; + let (address, limit) = command_input.split_once('|') + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, + "Latest address transactions input must be address|limit"))?; + let address_bytes = Wallet::normalize_to_short_address(address) + .and_then(|s| Wallet::short_address_to_bytes(&s)) + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "Invalid wallet address"))?; + let limit = limit.parse::() + .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "Invalid history limit"))?; + bin_msg.extend_from_slice(&command_number.to_le_bytes()); + bin_msg.extend_from_slice(&hashmap_key); + bin_msg.extend_from_slice(&address_bytes); + bin_msg.extend_from_slice(&limit.to_le_bytes()); + } _ => { return Err(io::Error::new( io::ErrorKind::InvalidInput,