standalone tools fix for command 47

This commit is contained in:
viraladmin 2026-06-14 15:26:18 -06:00
parent 9cc17ac0b5
commit 7e99a98643
5 changed files with 98 additions and 22 deletions

View File

@ -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;

View File

@ -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<u8>, limit: u32, db: &Db) -> RpcResponse
RpcResponse::Binary(txids)
}
pub async fn latest_transactions(address_bytes: Vec<u8>, 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)
}

View File

@ -15,7 +15,7 @@ const HEADER_SIZE: u64 = VRF_BLOCK_BYTES as u64;
pub async fn request_transaction_by_txid(db: &Db, txid: Vec<u8>) -> 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<u8>) -> RpcResponse
pub async fn request_transaction_by_txid_with_block(db: &Db, txid: Vec<u8>) -> 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<u8>), 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<u8>) -> 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();

View File

@ -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,

View File

@ -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::<u32>()
.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,