From a4c4518adfcb308e558e7009421f8a53b452520f Mon Sep 17 00:00:00 2001 From: viraladmin <00purple@gmail.com> Date: Fri, 12 Jun 2026 10:27:28 -0600 Subject: [PATCH] added RPC explorer commands --- src/blocks/burn.rs | 4 +- src/blocks/collateral.rs | 4 +- src/blocks/issue_token.rs | 4 +- src/blocks/loan_payment.rs | 4 +- src/blocks/loans.rs | 4 +- src/blocks/marketing.rs | 4 +- src/blocks/nft.rs | 4 +- src/blocks/swap.rs | 4 +- src/blocks/token.rs | 4 +- src/blocks/transfer.rs | 4 +- src/blocks/vanity.rs | 4 +- src/orphans/save_blocks.rs | 2 +- .../undo_transactions/undo_borrower.rs | 27 ++- src/orphans/undo_transactions/undo_burn.rs | 6 + .../undo_transactions/undo_collateral.rs | 206 +++++++++--------- .../undo_transactions/undo_create_nft.rs | 2 + .../undo_transactions/undo_create_token.rs | 2 + .../undo_transactions/undo_issue_token.rs | 2 + .../undo_transactions/undo_loan_creation.rs | 2 + .../undo_transactions/undo_marketing.rs | 2 + src/orphans/undo_transactions/undo_rewards.rs | 2 + src/orphans/undo_transactions/undo_swap.rs | 2 + .../undo_transactions/undo_transfer.rs | 2 + src/orphans/undo_transactions/undo_vanity.rs | 2 + src/records/memory/connections.rs | 13 +- src/records/memory/mempool/schema.rs | 8 +- src/records/memory/mempool/selection.rs | 100 ++++++++- src/records/memory/network_mapping/add.rs | 29 ++- .../memory/network_mapping/persistence.rs | 9 +- src/records/memory/response_channels.rs | 9 +- src/records/record_chain/borrower_tx.rs | 12 + src/records/record_chain/burn_tx.rs | 8 + src/records/record_chain/collateral_tx.rs | 8 + src/records/record_chain/issue_token_tx.rs | 10 +- src/records/record_chain/lender_tx.rs | 12 + src/records/record_chain/marketing_tx.rs | 12 +- src/records/record_chain/mod.rs | 1 + src/records/record_chain/nft_tx.rs | 8 + src/records/record_chain/rewards_tx.rs | 12 +- src/records/record_chain/swap_tx.rs | 12 + src/records/record_chain/token_tx.rs | 10 +- src/records/record_chain/transfer_tx.rs | 12 + src/records/record_chain/vanity_tx.rs | 10 +- src/records/record_chain/wallet_tx_index.rs | 111 ++++++++++ src/rpc/client/handshake_processing.rs | 2 +- src/rpc/client/register_wallet.rs | 6 +- src/rpc/command_maps.rs | 3 + src/rpc/commands/add_network_node.rs | 4 +- src/rpc/commands/address_history.rs | 81 +++++++ src/rpc/commands/mod.rs | 2 + src/rpc/commands/random_node.rs | 2 +- src/rpc/commands/tx_submit.rs | 4 +- .../commands/wallet_registration_status.rs | 18 ++ src/rpc/server/rpc_command_loop.rs | 70 ++++++ .../connections/sending_request.rs | 81 ++++++- src/startup/network_broadcast.rs | 9 +- src/startup/remote_height.rs | 9 +- 57 files changed, 790 insertions(+), 210 deletions(-) create mode 100644 src/records/record_chain/wallet_tx_index.rs create mode 100644 src/rpc/commands/address_history.rs create mode 100644 src/rpc/commands/wallet_registration_status.rs diff --git a/src/blocks/burn.rs b/src/blocks/burn.rs index fe95f5f..1f5f465 100644 --- a/src/blocks/burn.rs +++ b/src/blocks/burn.rs @@ -181,8 +181,8 @@ impl BurnTransaction { let hash = &self.unsigned_burn.hash().await; let signature = &self.signature; - let client_handle = db_client().await?; - let client = client_handle.as_ref(); + let client_handle = db_client().await?; + let client = client_handle.as_ref(); client .execute( diff --git a/src/blocks/collateral.rs b/src/blocks/collateral.rs index 4df1635..3ac5d38 100644 --- a/src/blocks/collateral.rs +++ b/src/blocks/collateral.rs @@ -176,8 +176,8 @@ impl CollateralClaimTransaction { let signature = &self.signature; // Collateral-claim transactions remain in the mempool table until mined or removed. - let client_handle = db_client().await?; - let client = client_handle.as_ref(); + let client_handle = db_client().await?; + let client = client_handle.as_ref(); client .execute( diff --git a/src/blocks/issue_token.rs b/src/blocks/issue_token.rs index 0fb139a..695e1d2 100644 --- a/src/blocks/issue_token.rs +++ b/src/blocks/issue_token.rs @@ -182,8 +182,8 @@ impl IssueTokenTransaction { let signature = &self.signature; // Issue-token transactions remain in the mempool table until mined or removed. - let client_handle = db_client().await?; - let client = client_handle.as_ref(); + let client_handle = db_client().await?; + let client = client_handle.as_ref(); client .execute( diff --git a/src/blocks/loan_payment.rs b/src/blocks/loan_payment.rs index 11a774d..1b835c9 100644 --- a/src/blocks/loan_payment.rs +++ b/src/blocks/loan_payment.rs @@ -208,8 +208,8 @@ impl ContractPaymentTransaction { let signature = &self.signature; // Loan-payment transactions remain in the mempool table until mined or removed. - let client_handle = db_client().await?; - let client = client_handle.as_ref(); + let client_handle = db_client().await?; + let client = client_handle.as_ref(); client .execute( diff --git a/src/blocks/loans.rs b/src/blocks/loans.rs index c3bc99b..96ba5ec 100644 --- a/src/blocks/loans.rs +++ b/src/blocks/loans.rs @@ -326,8 +326,8 @@ impl LoanContractTransaction { let signature2 = &self.signature2; // Loan contracts remain in the mempool table until mined or removed. - let client_handle = db_client().await?; - let client = client_handle.as_ref(); + let client_handle = db_client().await?; + let client = client_handle.as_ref(); client .execute( diff --git a/src/blocks/marketing.rs b/src/blocks/marketing.rs index c0cce19..d91ad33 100644 --- a/src/blocks/marketing.rs +++ b/src/blocks/marketing.rs @@ -253,8 +253,8 @@ impl MarketingTransaction { let signature = &self.signature; // Marketing transactions remain in the mempool table until mined or removed. - let client_handle = db_client().await?; - let client = client_handle.as_ref(); + let client_handle = db_client().await?; + let client = client_handle.as_ref(); client .execute( diff --git a/src/blocks/nft.rs b/src/blocks/nft.rs index eef07ce..1362c1e 100644 --- a/src/blocks/nft.rs +++ b/src/blocks/nft.rs @@ -230,8 +230,8 @@ impl CreateNftTransaction { let signature = &self.signature; // NFT-creation transactions remain in the mempool table until mined or removed. - let client_handle = db_client().await?; - let client = client_handle.as_ref(); + let client_handle = db_client().await?; + let client = client_handle.as_ref(); client .execute( diff --git a/src/blocks/swap.rs b/src/blocks/swap.rs index b7ccd66..e6a9670 100644 --- a/src/blocks/swap.rs +++ b/src/blocks/swap.rs @@ -327,8 +327,8 @@ impl SwapTransaction { let signature2 = &self.signature2; // Swap transactions remain in the mempool table until mined or removed. - let client_handle = db_client().await?; - let client = client_handle.as_ref(); + let client_handle = db_client().await?; + let client = client_handle.as_ref(); client .execute( diff --git a/src/blocks/token.rs b/src/blocks/token.rs index e6e4388..63e897f 100644 --- a/src/blocks/token.rs +++ b/src/blocks/token.rs @@ -204,8 +204,8 @@ impl CreateTokenTransaction { let signature = &self.signature; // Token-creation transactions remain in the mempool table until mined or removed. - let client_handle = db_client().await?; - let client = client_handle.as_ref(); + let client_handle = db_client().await?; + let client = client_handle.as_ref(); client .execute( diff --git a/src/blocks/transfer.rs b/src/blocks/transfer.rs index 8840078..ffe87b3 100644 --- a/src/blocks/transfer.rs +++ b/src/blocks/transfer.rs @@ -226,8 +226,8 @@ impl TransferTransaction { let signature = &self.signature; // Transfer transactions remain in the mempool table until mined or removed. - let client_handle = db_client().await?; - let client = client_handle.as_ref(); + let client_handle = db_client().await?; + let client = client_handle.as_ref(); client .execute( diff --git a/src/blocks/vanity.rs b/src/blocks/vanity.rs index 943ace0..23dd276 100644 --- a/src/blocks/vanity.rs +++ b/src/blocks/vanity.rs @@ -182,8 +182,8 @@ impl VanityAddressTransaction { // Vanity transactions are written to the vanity mempool table // until the transaction is mined or removed. - let client_handle = db_client().await?; - let client = client_handle.as_ref(); + let client_handle = db_client().await?; + let client = client_handle.as_ref(); client .execute( diff --git a/src/orphans/save_blocks.rs b/src/orphans/save_blocks.rs index c628013..eaed5cf 100644 --- a/src/orphans/save_blocks.rs +++ b/src/orphans/save_blocks.rs @@ -3,10 +3,10 @@ use crate::orphans::structs::UndoTransactions; use crate::orphans::torrent_candidates::hydrate_torrent_candidates; use crate::records::block_height::get_block_height::get_height; use crate::records::memory::response_channels::reserve_entry_with_context; -use crate::rpc::command_maps::RPC_TORRENT_BY_HEIGHT; use crate::records::memory::torrent_status::{ get_torrent_status, set_torrent_status, TorrentStatus, }; +use crate::rpc::command_maps::RPC_TORRENT_BY_HEIGHT; use crate::torrent::structs::Torrent; use crate::torrent::torrenting_system::save_torrent::{ list_staged_torrents_for_height, read_staged_torrent, diff --git a/src/orphans/undo_transactions/undo_borrower.rs b/src/orphans/undo_transactions/undo_borrower.rs index b7538b4..bdec875 100644 --- a/src/orphans/undo_transactions/undo_borrower.rs +++ b/src/orphans/undo_transactions/undo_borrower.rs @@ -3,8 +3,9 @@ use crate::blocks::loans::LoanContractTransaction; use crate::common::network_paths_and_settings::block_extension_and_paths; use crate::decode; use crate::records::balance_sheet::operations::balance_sheet_operation_with_db; -use crate::records::record_chain::add_payments_db::remove_payment; -use crate::records::record_chain::nft_provenance::remove_nft_history_entry; +use crate::records::record_chain::add_payments_db::remove_payment; +use crate::records::record_chain::nft_provenance::remove_nft_history_entry; +use crate::records::record_chain::wallet_tx_index::remove_wallet_transaction_index; use crate::rpc::commands::transaction_by_txid::request_transaction_by_txid; use crate::rpc::responses::RpcResponse; use crate::sled::Db; @@ -79,22 +80,24 @@ pub async fn undo_borrower_transaction( let _ = balance_sheet_operation_with_db(db, borrower, payback_amount, &loan_coin, operand_addition); - // Remove the payment transaction lookup from the txid tree. - let txid_tree = db - .open_tree("txid") - .map_err(|e| format!("Failed to open txid tree: {e}"))?; - let tx_hash = transaction.unsigned_contract_payment.hash().await; - txid_tree - .remove(decode(&tx_hash).map_err(|e| format!("Error decoding txid: {e}"))?) - .map_err(|e| format!("Failed to remove borrower txid: {e}"))?; + // Remove the payment transaction lookup from the txid tree. + let txid_tree = db + .open_tree("txid") + .map_err(|e| format!("Failed to open txid tree: {e}"))?; + let tx_hash = transaction.unsigned_contract_payment.hash().await; + let tx_hash_bytes = decode(&tx_hash).map_err(|e| format!("Error decoding txid: {e}"))?; + let _ = + remove_wallet_transaction_index(db, &[borrower, &lender, mining_receiver], &tx_hash_bytes); + txid_tree + .remove(tx_hash_bytes.clone()) + .map_err(|e| format!("Failed to remove borrower txid: {e}"))?; // Loan payments involving NFTs also add a provenance entry for the loan // asset, so remove it if this loan coin is tracked as an NFT. let nft_tree = db .open_tree("nfts") .map_err(|e| format!("Failed to open nfts tree: {e}"))?; - let tx_hash_bytes = decode(&tx_hash).map_err(|e| format!("Error decoding txid: {e}"))?; - if nft_tree.contains_key(loan_coin.as_bytes()).unwrap_or(false) { + if nft_tree.contains_key(loan_coin.as_bytes()).unwrap_or(false) { let _ = remove_nft_history_entry(db, &loan_coin, &tx_hash_bytes); } diff --git a/src/orphans/undo_transactions/undo_burn.rs b/src/orphans/undo_transactions/undo_burn.rs index 9e547bc..5a32200 100644 --- a/src/orphans/undo_transactions/undo_burn.rs +++ b/src/orphans/undo_transactions/undo_burn.rs @@ -5,6 +5,7 @@ use crate::decode; use crate::records::balance_sheet::operations::balance_sheet_operation_with_db; use crate::records::record_chain::nft_provenance::remove_nft_history_entry; use crate::records::record_chain::token_provenance::remove_token_history_entry; +use crate::records::record_chain::wallet_tx_index::remove_wallet_transaction_index; use crate::sled::Db; pub async fn undo_burn_transaction(transaction: BurnTransaction, mining_receiver: &str, db: &Db) { @@ -53,6 +54,11 @@ pub async fn undo_burn_transaction(transaction: BurnTransaction, mining_receiver ); let hash_binary = decode(&transaction.unsigned_burn.hash().await).unwrap(); + let _ = remove_wallet_transaction_index( + db, + &[&transaction.unsigned_burn.address, mining_receiver], + &hash_binary, + ); // Delete the txid lookup inserted when the burn was saved. let txid_tree = db.open_tree("txid").unwrap(); diff --git a/src/orphans/undo_transactions/undo_collateral.rs b/src/orphans/undo_transactions/undo_collateral.rs index 7e11707..17c500f 100644 --- a/src/orphans/undo_transactions/undo_collateral.rs +++ b/src/orphans/undo_transactions/undo_collateral.rs @@ -1,108 +1,108 @@ -use crate::blocks::collateral::CollateralClaimTransaction; -use crate::blocks::loans::LoanContractTransaction; +use crate::blocks::collateral::CollateralClaimTransaction; +use crate::blocks::loans::LoanContractTransaction; use crate::common::network_paths_and_settings::block_extension_and_paths; use crate::decode; use crate::records::balance_sheet::operations::balance_sheet_operation_with_db; -use crate::records::record_chain::nft_provenance::remove_nft_history_entry; -use crate::rpc::commands::transaction_by_txid::request_transaction_by_txid; -use crate::rpc::responses::RpcResponse; -use crate::sled::Db; - -pub async fn undo_collateral_transaction( - transaction: CollateralClaimTransaction, - mining_receiver: &str, - db: &Db, -) -> Result<(), String> { - // restore balances and contract state for a collateral - // claim that is being removed during orphan rollback - let operand_subtraction = "subtraction"; - let operand_addition = "addition"; - let ( - _network_name, - _padded_base_coin, - type_str, - _torrentpath, - _wallet_path, - _blockpath, - _db_path, - _balance_path, - _log_path, - ) = block_extension_and_paths(); - - // reload the original loan contract so the collateral - // asset and amount can be restored correctly - let contract_hash = decode(&transaction.unsigned_collateral_claim.contract_hash) - .map_err(|e| format!("Error decoding contract hash: {e}"))?; - let contract = request_transaction_by_txid(db, contract_hash.clone()).await; - - let loan_txtype = 7; - let loan_tx = match contract { - RpcResponse::Binary(contract_bytes) => { - if contract_bytes.is_empty() { - return Err("Invalid loan contract: empty transaction bytes".to_string()); - } - if contract_bytes[0] != loan_txtype { - return Err( - "Invalid loan contract: referenced transaction is not a loan contract" - .to_string(), - ); - } - LoanContractTransaction::from_bytes(loan_txtype, &contract_bytes[1..]) - .await - .map_err(|e| e.to_string())? - } - }; - - let collateral = loan_tx.unsigned_loan_contract.collateral; - let collateral_amount = loan_tx.unsigned_loan_contract.collateral_amount; - let collateral_holding = format!( - "collateral_{}", - transaction.unsigned_collateral_claim.contract_hash - ); - let claimer = &transaction.unsigned_collateral_claim.address; - let txfee = transaction.unsigned_collateral_claim.txfee; - - // reverse the fee and move the collateral back into the - // contract holding wallet until the claim exists again - let _ = - balance_sheet_operation_with_db(db, mining_receiver, txfee, &type_str, operand_subtraction); - let _ = balance_sheet_operation_with_db(db, claimer, txfee, &type_str, operand_addition); - let _ = balance_sheet_operation_with_db( - db, - claimer, - collateral_amount, - &collateral, - operand_subtraction, - ); - let _ = balance_sheet_operation_with_db( - db, - &collateral_holding, - collateral_amount, - &collateral, - operand_addition, - ); - - // Remove the collateral-claim transaction lookup from the txid tree. - let txid_tree = db.open_tree("txid").unwrap(); - let tx_hash = transaction.unsigned_collateral_claim.hash().await; - txid_tree - .remove(decode(&tx_hash).map_err(|e| format!("Error decoding txid: {e}"))?) - .unwrap(); - - // NFT collateral claims write provenance for the collateral asset. - let nft_tree = db.open_tree("nfts").unwrap(); - let tx_hash_bytes = decode(&tx_hash).map_err(|e| format!("Error decoding txid: {e}"))?; - if nft_tree - .contains_key(collateral.as_bytes()) - .unwrap_or(false) - { - let _ = remove_nft_history_entry(db, &collateral, &tx_hash_bytes); - } - - // Mark the loan contract active again because the collateral claim no - // longer exists after rollback. - let loan_tree = db.open_tree("loan").unwrap(); - loan_tree.insert(contract_hash, "true".as_bytes()).unwrap(); - +use crate::records::record_chain::nft_provenance::remove_nft_history_entry; +use crate::records::record_chain::wallet_tx_index::remove_wallet_transaction_index; +use crate::rpc::commands::transaction_by_txid::request_transaction_by_txid; +use crate::rpc::responses::RpcResponse; +use crate::sled::Db; + +pub async fn undo_collateral_transaction( + transaction: CollateralClaimTransaction, + mining_receiver: &str, + db: &Db, +) -> Result<(), String> { + // restore balances and contract state for a collateral + // claim that is being removed during orphan rollback + let operand_subtraction = "subtraction"; + let operand_addition = "addition"; + let ( + _network_name, + _padded_base_coin, + type_str, + _torrentpath, + _wallet_path, + _blockpath, + _db_path, + _balance_path, + _log_path, + ) = block_extension_and_paths(); + + // reload the original loan contract so the collateral + // asset and amount can be restored correctly + let contract_hash = decode(&transaction.unsigned_collateral_claim.contract_hash) + .map_err(|e| format!("Error decoding contract hash: {e}"))?; + let contract = request_transaction_by_txid(db, contract_hash.clone()).await; + + let loan_txtype = 7; + let loan_tx = match contract { + RpcResponse::Binary(contract_bytes) => { + if contract_bytes.is_empty() { + return Err("Invalid loan contract: empty transaction bytes".to_string()); + } + if contract_bytes[0] != loan_txtype { + return Err( + "Invalid loan contract: referenced transaction is not a loan contract" + .to_string(), + ); + } + LoanContractTransaction::from_bytes(loan_txtype, &contract_bytes[1..]) + .await + .map_err(|e| e.to_string())? + } + }; + + let collateral = loan_tx.unsigned_loan_contract.collateral; + let collateral_amount = loan_tx.unsigned_loan_contract.collateral_amount; + let collateral_holding = format!( + "collateral_{}", + transaction.unsigned_collateral_claim.contract_hash + ); + let claimer = &transaction.unsigned_collateral_claim.address; + let txfee = transaction.unsigned_collateral_claim.txfee; + + // reverse the fee and move the collateral back into the + // contract holding wallet until the claim exists again + let _ = + balance_sheet_operation_with_db(db, mining_receiver, txfee, &type_str, operand_subtraction); + let _ = balance_sheet_operation_with_db(db, claimer, txfee, &type_str, operand_addition); + let _ = balance_sheet_operation_with_db( + db, + claimer, + collateral_amount, + &collateral, + operand_subtraction, + ); + let _ = balance_sheet_operation_with_db( + db, + &collateral_holding, + collateral_amount, + &collateral, + operand_addition, + ); + + // Remove the collateral-claim transaction lookup from the txid tree. + let txid_tree = db.open_tree("txid").unwrap(); + let tx_hash = transaction.unsigned_collateral_claim.hash().await; + let tx_hash_bytes = decode(&tx_hash).map_err(|e| format!("Error decoding txid: {e}"))?; + let _ = remove_wallet_transaction_index(db, &[claimer, mining_receiver], &tx_hash_bytes); + txid_tree.remove(tx_hash_bytes.clone()).unwrap(); + + // NFT collateral claims write provenance for the collateral asset. + let nft_tree = db.open_tree("nfts").unwrap(); + if nft_tree + .contains_key(collateral.as_bytes()) + .unwrap_or(false) + { + let _ = remove_nft_history_entry(db, &collateral, &tx_hash_bytes); + } + + // Mark the loan contract active again because the collateral claim no + // longer exists after rollback. + let loan_tree = db.open_tree("loan").unwrap(); + loan_tree.insert(contract_hash, "true".as_bytes()).unwrap(); + Ok(()) } diff --git a/src/orphans/undo_transactions/undo_create_nft.rs b/src/orphans/undo_transactions/undo_create_nft.rs index 0c45f92..a10aa8f 100644 --- a/src/orphans/undo_transactions/undo_create_nft.rs +++ b/src/orphans/undo_transactions/undo_create_nft.rs @@ -4,6 +4,7 @@ use crate::common::nft_assets::nft_asset_name; use crate::decode; use crate::records::balance_sheet::operations::balance_sheet_operation_with_db; use crate::records::record_chain::nft_provenance::{remove_nft_history_entry, remove_nft_origin}; +use crate::records::record_chain::wallet_tx_index::remove_wallet_transaction_index; use crate::sled::Db; const NFT_UNIT: u64 = 100_000_000; @@ -43,6 +44,7 @@ pub async fn undo_create_nft_transaction( ); let _ = balance_sheet_operation_with_db(db, creator, *txfee, &type_str, operand_addition); let hash_binary = decode(&transaction.unsigned_create_nft.hash().await).unwrap(); + let _ = remove_wallet_transaction_index(db, &[creator, mining_receiver], &hash_binary); // Remove the create-NFT transaction lookup from the txid tree. let tree = db.open_tree("txid").unwrap(); diff --git a/src/orphans/undo_transactions/undo_create_token.rs b/src/orphans/undo_transactions/undo_create_token.rs index 6928ed4..41dade3 100644 --- a/src/orphans/undo_transactions/undo_create_token.rs +++ b/src/orphans/undo_transactions/undo_create_token.rs @@ -3,6 +3,7 @@ use crate::common::network_paths_and_settings::block_extension_and_paths; use crate::decode; use crate::records::balance_sheet::operations::balance_sheet_operation_with_db; use crate::records::record_chain::token_provenance::clear_token_history; +use crate::records::record_chain::wallet_tx_index::remove_wallet_transaction_index; use crate::sled::Db; pub async fn undo_create_token_transaction( @@ -46,6 +47,7 @@ pub async fn undo_create_token_transaction( let ticker_binary = &transaction.unsigned_create_token.ticker.as_bytes(); let hash_binary = decode(&transaction.unsigned_create_token.hash().await).unwrap(); + let _ = remove_wallet_transaction_index(db, &[creator, mining_receiver], &hash_binary); // Remove the create-token transaction lookup from the txid tree. let tree = db.open_tree("txid").unwrap(); diff --git a/src/orphans/undo_transactions/undo_issue_token.rs b/src/orphans/undo_transactions/undo_issue_token.rs index 5b7080a..a0eb28f 100644 --- a/src/orphans/undo_transactions/undo_issue_token.rs +++ b/src/orphans/undo_transactions/undo_issue_token.rs @@ -3,6 +3,7 @@ use crate::common::network_paths_and_settings::block_extension_and_paths; use crate::decode; use crate::records::balance_sheet::operations::balance_sheet_operation_with_db; use crate::records::record_chain::token_provenance::remove_token_history_entry; +use crate::records::record_chain::wallet_tx_index::remove_wallet_transaction_index; use crate::sled::Db; pub async fn undo_issue_token_transaction( @@ -43,6 +44,7 @@ pub async fn undo_issue_token_transaction( let _ = balance_sheet_operation_with_db(db, creator, *number, ticker, operand_subtraction); let hash_binary = decode(&transaction.unsigned_issue_token.hash().await).unwrap(); + let _ = remove_wallet_transaction_index(db, &[creator, mining_receiver], &hash_binary); // Delete the issued-token transaction lookup and provenance record. let txid_tree = db.open_tree("txid").unwrap(); diff --git a/src/orphans/undo_transactions/undo_loan_creation.rs b/src/orphans/undo_transactions/undo_loan_creation.rs index 9dc1a23..842c51d 100644 --- a/src/orphans/undo_transactions/undo_loan_creation.rs +++ b/src/orphans/undo_transactions/undo_loan_creation.rs @@ -3,6 +3,7 @@ use crate::common::network_paths_and_settings::block_extension_and_paths; use crate::decode; use crate::records::balance_sheet::operations::balance_sheet_operation_with_db; use crate::records::record_chain::nft_provenance::remove_nft_history_entry; +use crate::records::record_chain::wallet_tx_index::remove_wallet_transaction_index; use crate::sled::Db; pub async fn undo_loan_creation_transaction( @@ -66,6 +67,7 @@ pub async fn undo_loan_creation_transaction( ); let hash_binary = decode(hash).unwrap(); + let _ = remove_wallet_transaction_index(db, &[lender, borrower, mining_receiver], &hash_binary); // delete the txid and remove the active loan record // so the contract no longer exists on-chain diff --git a/src/orphans/undo_transactions/undo_marketing.rs b/src/orphans/undo_transactions/undo_marketing.rs index c077dd0..279847f 100644 --- a/src/orphans/undo_transactions/undo_marketing.rs +++ b/src/orphans/undo_transactions/undo_marketing.rs @@ -2,6 +2,7 @@ use crate::blocks::marketing::MarketingTransaction; use crate::common::network_paths_and_settings::block_extension_and_paths; use crate::decode; use crate::records::balance_sheet::operations::balance_sheet_operation_with_db; +use crate::records::record_chain::wallet_tx_index::remove_wallet_transaction_index; use crate::sled::Db; pub async fn undo_marketing_transaction( @@ -40,6 +41,7 @@ pub async fn undo_marketing_transaction( let _ = balance_sheet_operation_with_db(db, advertiser, *txfee, &type_str, operand_addition); let hash = decode(&transaction.unsigned_marketing.hash().await).unwrap(); + let _ = remove_wallet_transaction_index(db, &[advertiser, mining_receiver], &hash); // Remove the marketing transaction lookup from the txid tree. let tree = db.open_tree("txid").unwrap(); diff --git a/src/orphans/undo_transactions/undo_rewards.rs b/src/orphans/undo_transactions/undo_rewards.rs index 38a5b24..9686a21 100644 --- a/src/orphans/undo_transactions/undo_rewards.rs +++ b/src/orphans/undo_transactions/undo_rewards.rs @@ -5,6 +5,7 @@ use crate::records::balance_sheet::operations::balance_sheet_operation_with_db; use crate::records::record_chain::rewards_tx::{ remove_reward_credit_marker, reward_credit_applied, }; +use crate::records::record_chain::wallet_tx_index::remove_wallet_transaction_index; use crate::sled::Db; pub async fn undo_rewards_transaction( @@ -38,6 +39,7 @@ pub async fn undo_rewards_transaction( } let hash = decode(transaction.unsigned.hash().await).unwrap(); + let _ = remove_wallet_transaction_index(db, &[mining_receiver], &hash); // Remove the reward transaction lookup from the txid tree. let tree = db.open_tree("txid").unwrap(); diff --git a/src/orphans/undo_transactions/undo_swap.rs b/src/orphans/undo_transactions/undo_swap.rs index b411766..f70e42b 100644 --- a/src/orphans/undo_transactions/undo_swap.rs +++ b/src/orphans/undo_transactions/undo_swap.rs @@ -4,6 +4,7 @@ use crate::common::nft_assets::nft_asset_name; use crate::decode; use crate::records::balance_sheet::operations::balance_sheet_operation_with_db; use crate::records::record_chain::nft_provenance::remove_nft_history_entry; +use crate::records::record_chain::wallet_tx_index::remove_wallet_transaction_index; use crate::sled::Db; pub async fn undo_swap_transaction(transaction: SwapTransaction, mining_receiver: &str, db: &Db) { @@ -84,6 +85,7 @@ pub async fn undo_swap_transaction(transaction: SwapTransaction, mining_receiver // Convert the txid hash back to bytes for tree lookup/removal. let hash = decode(&transaction.unsigned_swap.hash().await).unwrap(); + let _ = remove_wallet_transaction_index(db, &[sender1, sender2, mining_receiver], &hash); // Remove the txid lookup for the rolled-back swap. let tree = db.open_tree("txid").unwrap(); diff --git a/src/orphans/undo_transactions/undo_transfer.rs b/src/orphans/undo_transactions/undo_transfer.rs index a39cf1c..5410fa3 100644 --- a/src/orphans/undo_transactions/undo_transfer.rs +++ b/src/orphans/undo_transactions/undo_transfer.rs @@ -4,6 +4,7 @@ use crate::common::nft_assets::nft_asset_name; use crate::decode; use crate::records::balance_sheet::operations::balance_sheet_operation_with_db; use crate::records::record_chain::nft_provenance::remove_nft_history_entry; +use crate::records::record_chain::wallet_tx_index::remove_wallet_transaction_index; use crate::sled::Db; pub async fn undo_transfer_transaction( @@ -51,6 +52,7 @@ pub async fn undo_transfer_transaction( let _ = balance_sheet_operation_with_db(db, sender, *txfee, &type_str, operand_addition); let hash = decode(&transaction.unsigned_transfer.hash().await).unwrap(); + let _ = remove_wallet_transaction_index(db, &[sender, receiver, mining_receiver], &hash); // Remove the txid lookup so the rolled-back transfer no longer resolves as // an on-chain transaction. diff --git a/src/orphans/undo_transactions/undo_vanity.rs b/src/orphans/undo_transactions/undo_vanity.rs index 8504deb..27866bd 100644 --- a/src/orphans/undo_transactions/undo_vanity.rs +++ b/src/orphans/undo_transactions/undo_vanity.rs @@ -2,6 +2,7 @@ use crate::blocks::vanity::VanityAddressTransaction; use crate::decode; use crate::records::balance_sheet::operations::balance_sheet_operation_with_db; use crate::records::memory::mempool::BASECOIN; +use crate::records::record_chain::wallet_tx_index::remove_wallet_transaction_index; use crate::records::wallet_registry::{ register_or_update_vanity_address, remove_registered_vanity_for_owner, take_previous_vanity_for_txid, VanityRegistrationResult, @@ -63,6 +64,7 @@ pub async fn undo_vanity_transaction( .map_err(|err| format!("Could not open txid tree during vanity undo: {err}"))?; let txkey = decode(&txhash) .map_err(|err| format!("Could not decode vanity txhash during undo: {err}"))?; + let _ = remove_wallet_transaction_index(db, &[&owner_address, mining_receiver], &txkey); tree.remove(txkey) .map_err(|err| format!("Could not remove vanity txid mapping during undo: {err}"))?; diff --git a/src/records/memory/connections.rs b/src/records/memory/connections.rs index d70d21c..a76dc65 100644 --- a/src/records/memory/connections.rs +++ b/src/records/memory/connections.rs @@ -351,13 +351,12 @@ impl Connection { } let message_type = RPC_BLOCK_HEIGHT; // Block-height request used as a lightweight checkup ping. - let (checkup_key, _checkup_tx, checkup_rx_mutex) = - reserve_entry_with_context( - command_map.clone(), - Some(RPC_BLOCK_HEIGHT), - Some(format!("{ip}:{port}")), - ) - .await; + let (checkup_key, _checkup_tx, checkup_rx_mutex) = reserve_entry_with_context( + command_map.clone(), + Some(RPC_BLOCK_HEIGHT), + Some(format!("{ip}:{port}")), + ) + .await; // Send a lightweight ping message and wait for the reply // routed back through the shared response hashmap. diff --git a/src/records/memory/mempool/schema.rs b/src/records/memory/mempool/schema.rs index a0c61e9..ff7c009 100644 --- a/src/records/memory/mempool/schema.rs +++ b/src/records/memory/mempool/schema.rs @@ -27,9 +27,7 @@ async fn connect_client() -> Result { } pub async fn db_client() -> Result> { - let slot = DB - .get() - .ok_or_else(|| anyhow!("DB not initialized"))?; + let slot = DB.get().ok_or_else(|| anyhow!("DB not initialized"))?; Ok(slot.read().await.clone()) } @@ -69,7 +67,7 @@ pub async fn init_db() -> Result<()> { pub async fn setup_mempool() -> Result<()> { // Create or migrate the mempool schema, deduplicate any stale rows, // add the selection indexes, and start from an empty live mempool. - let client_handle = db_client().await?; + let client_handle = db_client().await?; let client = client_handle.as_ref(); let schema = r#" @@ -404,7 +402,7 @@ pub async fn setup_mempool() -> Result<()> { pub async fn clear_mempool() -> Result<()> { // Startup clears any leftover mempool rows so a node restart begins // from a clean pending-transaction state. - let client_handle = db_client().await?; + let client_handle = db_client().await?; let client = client_handle.as_ref(); client diff --git a/src/records/memory/mempool/selection.rs b/src/records/memory/mempool/selection.rs index 0e39ee4..4a6539a 100644 --- a/src/records/memory/mempool/selection.rs +++ b/src/records/memory/mempool/selection.rs @@ -1,10 +1,24 @@ use super::*; use crate::records::record_chain::pending_effects::{BalanceOperand, PendingEffects}; +use crate::records::record_chain::wallet_tx_index::index_wallet_transaction; + +fn index_selected_wallet_transaction( + pending_effects: &mut PendingEffects, + addresses: &[&str], + block_height: u32, + entry_index: usize, + txid: &[u8], +) -> Result<()> { + let entry_index = + u32::try_from(entry_index).map_err(|_| anyhow!("Wallet transaction index overflowed"))?; + index_wallet_transaction(pending_effects, addresses, block_height, entry_index, txid) + .map_err(|err| anyhow!(err)) +} pub async fn select_transactions_for_block(limit: i64) -> Result { // Pull the highest-priority unprocessed rows across all mempool // tables, keeping the original bytes for block-file streaming later. - let client_handle = db_client().await?; + let client_handle = db_client().await?; let client = client_handle.as_ref(); let rows = client .query( @@ -383,6 +397,13 @@ pub async fn apply_selected_transaction_math( decode(hash)?, format!("{block_number}:{tx_index}").into_bytes(), ); + index_selected_wallet_transaction( + pending_effects, + &[sender, receiver, &miner], + block_number, + tx_index, + &decode(hash)?, + )?; } SelectedMempoolTransaction::Token { fee, @@ -412,8 +433,8 @@ pub async fn apply_selected_transaction_math( // Local mined blocks need to persist the token hard-limit // metadata just like the downloaded-block save path does. - let client_handle = db_client().await?; - let client = client_handle.as_ref(); + let client_handle = db_client().await?; + let client = client_handle.as_ref(); let token_row = client .query_opt( "SELECT hard_limit FROM token WHERE hash = $1 LIMIT 1", @@ -443,6 +464,13 @@ pub async fn apply_selected_transaction_math( decode(hash)?, format!("{block_number}:{tx_index}").into_bytes(), ); + index_selected_wallet_transaction( + pending_effects, + &[creator, &miner], + block_number, + tx_index, + &decode(hash)?, + )?; } SelectedMempoolTransaction::IssueToken { fee, @@ -474,6 +502,13 @@ pub async fn apply_selected_transaction_math( decode(hash)?, format!("{block_number}:{tx_index}").into_bytes(), ); + index_selected_wallet_transaction( + pending_effects, + &[creator, &miner], + block_number, + tx_index, + &decode(hash)?, + )?; } SelectedMempoolTransaction::Burn { fee, @@ -502,6 +537,13 @@ pub async fn apply_selected_transaction_math( decode(hash)?, format!("{block_number}:{tx_index}").into_bytes(), ); + index_selected_wallet_transaction( + pending_effects, + &[address, &miner], + block_number, + tx_index, + &decode(hash)?, + )?; } SelectedMempoolTransaction::Nft { fee, @@ -568,6 +610,13 @@ pub async fn apply_selected_transaction_math( decode(hash)?, format!("{block_number}:{tx_index}").into_bytes(), ); + index_selected_wallet_transaction( + pending_effects, + &[creator, &miner], + block_number, + tx_index, + &decode(hash)?, + )?; } SelectedMempoolTransaction::Marketing { fee, @@ -587,6 +636,13 @@ pub async fn apply_selected_transaction_math( decode(hash)?, format!("{block_number}:{tx_index}").into_bytes(), ); + index_selected_wallet_transaction( + pending_effects, + &[advertiser, &miner], + block_number, + tx_index, + &decode(hash)?, + )?; } SelectedMempoolTransaction::Vanity { fee, @@ -610,6 +666,13 @@ pub async fn apply_selected_transaction_math( decode(hash)?, format!("{block_number}:{tx_index}").into_bytes(), ); + index_selected_wallet_transaction( + pending_effects, + &[address, &miner], + block_number, + tx_index, + &decode(hash)?, + )?; } SelectedMempoolTransaction::Swap { fee1, @@ -683,6 +746,13 @@ pub async fn apply_selected_transaction_math( decode(hash)?, format!("{block_number}:{tx_index}").into_bytes(), ); + index_selected_wallet_transaction( + pending_effects, + &[sender1, sender2, &miner], + block_number, + tx_index, + &decode(hash)?, + )?; } SelectedMempoolTransaction::Lender { fee, @@ -745,6 +815,13 @@ pub async fn apply_selected_transaction_math( decode(hash)?, format!("{block_number}:{tx_index}").into_bytes(), ); + index_selected_wallet_transaction( + pending_effects, + &[lender, borrower, &miner], + block_number, + tx_index, + &decode(hash)?, + )?; } SelectedMempoolTransaction::Borrower { fee, @@ -806,6 +883,14 @@ pub async fn apply_selected_transaction_math( decode(hash)?, format!("{block_number}:{tx_index}").into_bytes(), ); + let lender_address = binary_to_string(lender.clone()); + index_selected_wallet_transaction( + pending_effects, + &[address, &lender_address, &miner], + block_number, + tx_index, + &decode(hash)?, + )?; } SelectedMempoolTransaction::Collateral { fee, @@ -854,6 +939,13 @@ pub async fn apply_selected_transaction_math( decode(hash)?, format!("{block_number}:{tx_index}").into_bytes(), ); + index_selected_wallet_transaction( + pending_effects, + &[address, &miner], + block_number, + tx_index, + &decode(hash)?, + )?; } } } @@ -924,7 +1016,7 @@ pub async fn stream_selected_transaction_originals( } pub async fn delete_selected_transactions(batch: &SelectedMempoolBatch) -> Result<()> { - let client_handle = db_client().await?; + let client_handle = db_client().await?; let client = client_handle.as_ref(); // Each transaction kind still lives in a separate SQL table, so deletion diff --git a/src/records/memory/network_mapping/add.rs b/src/records/memory/network_mapping/add.rs index f79b4b6..5ae6974 100644 --- a/src/records/memory/network_mapping/add.rs +++ b/src/records/memory/network_mapping/add.rs @@ -335,7 +335,9 @@ impl NodeInfo { } if !address_map.contains_key(&edit.address) { - if let Some(existing_node) = address_map.values_mut().find(|node| node.ip == edit.ip) { + if let Some(existing_node) = + address_map.values_mut().find(|node| node.ip == edit.ip) + { if existing_node.deleted_timestamp == 0 && edit.ip != GENESIS_IP { penalize_duplicate_ip = true; } @@ -344,20 +346,17 @@ impl NodeInfo { if !penalize_duplicate_ip { // Persist the new node locally. Network-map entries are bare // IP membership records, separate from live socket keys. - address_map.insert( - edit.address.clone(), - { - let mut node = NodeInfo::new( - edit.ip.clone(), - blocks_mined, - edit.modified_by.clone(), - edit.modified_timestamp, - edit.modified_signature.clone(), - ); - node.monitoring = monitors.clone(); - node - }, - ); + address_map.insert(edit.address.clone(), { + let mut node = NodeInfo::new( + edit.ip.clone(), + blocks_mined, + edit.modified_by.clone(), + edit.modified_timestamp, + edit.modified_signature.clone(), + ); + node.monitoring = monitors.clone(); + node + }); state_changed = true; } } diff --git a/src/records/memory/network_mapping/persistence.rs b/src/records/memory/network_mapping/persistence.rs index 1937f1f..215da85 100644 --- a/src/records/memory/network_mapping/persistence.rs +++ b/src/records/memory/network_mapping/persistence.rs @@ -113,13 +113,8 @@ impl NodeInfo { .unwrap(), ); - let mut node = NodeInfo::new( - ip, - blocks_mined, - added_by, - added_timestamp, - added_signature, - ); + let mut node = + NodeInfo::new(ip, blocks_mined, added_by, added_timestamp, added_signature); node.deleted_timestamp = deleted_timestamp; node.deleted_block = deleted_block; node.monitoring = Vec::new(); diff --git a/src/records/memory/response_channels.rs b/src/records/memory/response_channels.rs index 369e79c..755c5a2 100644 --- a/src/records/memory/response_channels.rs +++ b/src/records/memory/response_channels.rs @@ -112,12 +112,13 @@ pub async fn reserve_transient_entry_with_context( tokio::spawn(async move { let received = { let mut rx = rx.lock().await; - matches!(crate::timeout(Duration::from_secs(30), rx.recv()).await, Ok(Some(_))) + matches!( + crate::timeout(Duration::from_secs(30), rx.recv()).await, + Ok(Some(_)) + ) }; if !received { - warn!( - "[rpc_trace] transient uid expired without reply: uid={key:?}" - ); + warn!("[rpc_trace] transient uid expired without reply: uid={key:?}"); } delete_entry(map, key).await; }); diff --git a/src/records/record_chain/borrower_tx.rs b/src/records/record_chain/borrower_tx.rs index 56088c9..08a62f9 100644 --- a/src/records/record_chain/borrower_tx.rs +++ b/src/records/record_chain/borrower_tx.rs @@ -3,6 +3,7 @@ use crate::blocks::loans::LoanContractTransaction; use crate::decode; use crate::records::memory::mempool::BASECOIN; use crate::records::record_chain::pending_effects::{BalanceOperand, PendingEffects}; +use crate::records::record_chain::wallet_tx_index::index_wallet_transaction; use crate::rpc::commands::transaction_by_txid::request_transaction_by_txid; use crate::rpc::responses::RpcResponse; use crate::sled::Db; @@ -106,6 +107,17 @@ pub async fn process_borrower( let txkey = decode(txhash).map_err(|e| format!("Error decoding borrower txid key: {e}"))?; let txvalue = format!("{block_header_number}:{}", **index); pending_effects.set_tree("txid", txkey, txvalue.into_bytes()); + index_wallet_transaction( + pending_effects, + &[ + &transaction.unsigned_contract_payment.address, + &lender, + &miner, + ], + block_header_number, + **index as u32, + &txhash_bytes, + )?; pending_effects.append_tree_if_key_exists( "nfts", "nft_history", diff --git a/src/records/record_chain/burn_tx.rs b/src/records/record_chain/burn_tx.rs index 82bb8df..ffc550c 100644 --- a/src/records/record_chain/burn_tx.rs +++ b/src/records/record_chain/burn_tx.rs @@ -3,6 +3,7 @@ use crate::common::nft_assets::nft_asset_name; use crate::decode; use crate::records::memory::mempool::BASECOIN; use crate::records::record_chain::pending_effects::{BalanceOperand, PendingEffects}; +use crate::records::record_chain::wallet_tx_index::index_wallet_transaction; use crate::sled::Db; use crate::Arc; use crate::Mutex; @@ -70,6 +71,13 @@ pub async fn process_burn( let txkey = decode(txhash).map_err(|e| format!("Error decoding burn txid key: {e}"))?; let txvalue = format!("{block_header_number}:{}", **index); pending_effects.set_tree("txid", txkey, txvalue.into_bytes()); + index_wallet_transaction( + pending_effects, + &[&transaction.unsigned_burn.address, &miner], + block_header_number, + **index as u32, + &txhash_bytes, + )?; Ok(binary_data) } diff --git a/src/records/record_chain/collateral_tx.rs b/src/records/record_chain/collateral_tx.rs index 97c4381..0dd3007 100644 --- a/src/records/record_chain/collateral_tx.rs +++ b/src/records/record_chain/collateral_tx.rs @@ -3,6 +3,7 @@ use crate::blocks::loans::LoanContractTransaction; use crate::decode; use crate::records::memory::mempool::BASECOIN; use crate::records::record_chain::pending_effects::{BalanceOperand, PendingEffects}; +use crate::records::record_chain::wallet_tx_index::index_wallet_transaction; use crate::rpc::commands::transaction_by_txid::request_transaction_by_txid; use crate::rpc::responses::RpcResponse; use crate::sled::Db; @@ -96,6 +97,13 @@ pub async fn process_collateral( let txkey = decode(txhash).map_err(|e| format!("Error decoding hex: {e}"))?; let txvalue = format!("{block_header_number}:{}", **index); pending_effects.set_tree("txid", txkey, txvalue.into_bytes()); + index_wallet_transaction( + pending_effects, + &[&transaction.unsigned_collateral_claim.address, &miner], + block_header_number, + **index as u32, + &txhash_bytes, + )?; pending_effects.append_tree_if_key_exists( "nfts", "nft_history", diff --git a/src/records/record_chain/issue_token_tx.rs b/src/records/record_chain/issue_token_tx.rs index 87703c8..eb36d3b 100644 --- a/src/records/record_chain/issue_token_tx.rs +++ b/src/records/record_chain/issue_token_tx.rs @@ -2,6 +2,7 @@ use crate::blocks::issue_token::IssueTokenTransaction; use crate::decode; use crate::records::memory::mempool::BASECOIN; use crate::records::record_chain::pending_effects::{BalanceOperand, PendingEffects}; +use crate::records::record_chain::wallet_tx_index::index_wallet_transaction; use crate::sled::Db; use crate::Arc; use crate::Mutex; @@ -66,7 +67,14 @@ pub async fn process_issue_token( // transaction back to its block and index position. let txkey = txhash_bytes; let txvalue = format!("{block_header_number}:{}", **index); - pending_effects.set_tree("txid", txkey, txvalue.into_bytes()); + pending_effects.set_tree("txid", txkey.clone(), txvalue.into_bytes()); + index_wallet_transaction( + pending_effects, + &[&transaction.unsigned_issue_token.creator, &miner], + block_header_number, + **index as u32, + &txkey, + )?; Ok(binary_data) } diff --git a/src/records/record_chain/lender_tx.rs b/src/records/record_chain/lender_tx.rs index dd3b047..9a93357 100644 --- a/src/records/record_chain/lender_tx.rs +++ b/src/records/record_chain/lender_tx.rs @@ -2,6 +2,7 @@ use crate::blocks::loans::LoanContractTransaction; use crate::decode; use crate::records::memory::mempool::BASECOIN; use crate::records::record_chain::pending_effects::{BalanceOperand, PendingEffects}; +use crate::records::record_chain::wallet_tx_index::index_wallet_transaction; use crate::sled::Db; use crate::Arc; use crate::Mutex; @@ -75,6 +76,17 @@ pub async fn process_lender( let txkey = decode(&txhash).map_err(|e| format!("Error decoding hex: {e}"))?; let txvalue = format!("{block_header_number}:{}", **index); pending_effects.set_tree("txid", txkey, txvalue.into_bytes()); + index_wallet_transaction( + pending_effects, + &[ + &transaction.unsigned_loan_contract.lender, + &transaction.unsigned_loan_contract.borrower, + &miner, + ], + block_header_number, + **index as u32, + &txhash_bytes, + )?; let loankey = decode(&txhash).map_err(|e| format!("Error decoding hex: {e}"))?; pending_effects.set_tree("loan", loankey, b"true".to_vec()); pending_effects.append_tree_if_key_exists( diff --git a/src/records/record_chain/marketing_tx.rs b/src/records/record_chain/marketing_tx.rs index a743502..edfc469 100644 --- a/src/records/record_chain/marketing_tx.rs +++ b/src/records/record_chain/marketing_tx.rs @@ -2,6 +2,7 @@ use crate::blocks::marketing::MarketingTransaction; use crate::decode; use crate::records::memory::mempool::BASECOIN; use crate::records::record_chain::pending_effects::{BalanceOperand, PendingEffects}; +use crate::records::record_chain::wallet_tx_index::index_wallet_transaction; use crate::sled::Db; use crate::Arc; use crate::Mutex; @@ -23,6 +24,8 @@ pub async fn process_marketing( // Serialize the marketing transaction and compute its txid before // applying the fee transfer side effects. let txhash = transaction.unsigned_marketing.hash().await; + let txhash_bytes = + decode(&txhash).map_err(|e| format!("Error decoding marketing txhash: {e}"))?; let transaction_bytes = match transaction.to_bytes().await { Ok(bytes) => bytes, Err(e) => return Err(e.to_string()), @@ -46,8 +49,15 @@ pub async fn process_marketing( // Record the txid location so RPC lookups can resolve the saved // transaction back to its block and index position. - let txkey = decode(txhash).map_err(|e| format!("Error decoding marketing txid key: {e}"))?; + let txkey = txhash_bytes.clone(); let txvalue = format!("{block_header_number}:{}", **index); pending_effects.set_tree("txid", txkey, txvalue.into_bytes()); + index_wallet_transaction( + pending_effects, + &[&transaction.unsigned_marketing.advertiser, &miner], + block_header_number, + **index as u32, + &txhash_bytes, + )?; Ok(binary_data) } diff --git a/src/records/record_chain/mod.rs b/src/records/record_chain/mod.rs index 553bd2e..c65fbe7 100644 --- a/src/records/record_chain/mod.rs +++ b/src/records/record_chain/mod.rs @@ -22,3 +22,4 @@ pub mod token_provenance; pub mod token_tx; pub mod transfer_tx; pub mod vanity_tx; +pub mod wallet_tx_index; diff --git a/src/records/record_chain/nft_tx.rs b/src/records/record_chain/nft_tx.rs index ccb3d0f..13fd1be 100644 --- a/src/records/record_chain/nft_tx.rs +++ b/src/records/record_chain/nft_tx.rs @@ -3,6 +3,7 @@ use crate::common::nft_assets::nft_asset_name; use crate::decode; use crate::records::memory::mempool::BASECOIN; use crate::records::record_chain::pending_effects::{BalanceOperand, PendingEffects}; +use crate::records::record_chain::wallet_tx_index::index_wallet_transaction; use crate::sled::Db; use crate::Arc; use crate::Mutex; @@ -97,6 +98,13 @@ pub async fn process_nft( let txkey = decode(txhash).map_err(|e| format!("Error decoding nft txid key: {e}"))?; let txvalue = format!("{block_header_number}:{}", **index); pending_effects.set_tree("txid", txkey, txvalue.into_bytes()); + index_wallet_transaction( + pending_effects, + &[&transaction.unsigned_create_nft.creator, &miner], + block_header_number, + **index as u32, + &txhash_bytes, + )?; Ok(binary_data) } diff --git a/src/records/record_chain/rewards_tx.rs b/src/records/record_chain/rewards_tx.rs index 794e272..d7f8c4c 100644 --- a/src/records/record_chain/rewards_tx.rs +++ b/src/records/record_chain/rewards_tx.rs @@ -4,6 +4,7 @@ use crate::decode; use crate::records::balance_sheet::operations::balance_sheet_operation_with_db; use crate::records::memory::mempool::BASECOIN; use crate::records::record_chain::pending_effects::PendingEffects; +use crate::records::record_chain::wallet_tx_index::index_wallet_transaction; use crate::records::unpack_block::load_by_block_number::load_block; use crate::sled::Db; use crate::Arc; @@ -72,7 +73,7 @@ pub async fn process_rewards( mut binary_data: Vec, _db: &Db, index_counter: Arc>, - _miner: String, + miner: String, block_header_number: u32, pending_effects: &mut PendingEffects, ) -> Result, String> { @@ -94,6 +95,13 @@ pub async fn process_rewards( // block/index, but does not make the reward spendable yet. let txkey = decode(txhash).map_err(|e| format!("Error decoding rewards txid key: {e}"))?; let txvalue = format!("{block_header_number}:{}", **index); - pending_effects.set_tree("txid", txkey, txvalue.into_bytes()); + pending_effects.set_tree("txid", txkey.clone(), txvalue.into_bytes()); + index_wallet_transaction( + pending_effects, + &[&miner], + block_header_number, + **index as u32, + &txkey, + )?; Ok(binary_data) } diff --git a/src/records/record_chain/swap_tx.rs b/src/records/record_chain/swap_tx.rs index 71cf9f9..732fcdf 100644 --- a/src/records/record_chain/swap_tx.rs +++ b/src/records/record_chain/swap_tx.rs @@ -3,6 +3,7 @@ use crate::common::nft_assets::nft_asset_name; use crate::decode; use crate::records::memory::mempool::BASECOIN; use crate::records::record_chain::pending_effects::{BalanceOperand, PendingEffects}; +use crate::records::record_chain::wallet_tx_index::index_wallet_transaction; use crate::sled::Db; use crate::Arc; use crate::Mutex; @@ -120,6 +121,17 @@ pub async fn process_swap( let txkey = decode(txhash).map_err(|e| format!("Error decoding swap txid key: {e}"))?; let txvalue = format!("{block_header_number}:{}", **index); pending_effects.set_tree("txid", txkey, txvalue.into_bytes()); + index_wallet_transaction( + pending_effects, + &[ + &transaction.unsigned_swap.sender1, + &transaction.unsigned_swap.sender2, + &miner, + ], + block_header_number, + **index as u32, + &txhash_bytes, + )?; pending_effects.append_tree_if_key_exists( "nfts", "nft_history", diff --git a/src/records/record_chain/token_tx.rs b/src/records/record_chain/token_tx.rs index 722c227..6216f63 100644 --- a/src/records/record_chain/token_tx.rs +++ b/src/records/record_chain/token_tx.rs @@ -2,6 +2,7 @@ use crate::blocks::token::CreateTokenTransaction; use crate::decode; use crate::records::memory::mempool::BASECOIN; use crate::records::record_chain::pending_effects::{BalanceOperand, PendingEffects}; +use crate::records::record_chain::wallet_tx_index::index_wallet_transaction; use crate::sled::Db; use crate::Arc; use crate::Mutex; @@ -77,6 +78,13 @@ pub async fn process_token( // transaction back to its block and index position. let txkey = txhash_bytes; let txvalue = format!("{block_header_number}:{}", **index); - pending_effects.set_tree("txid", txkey, txvalue.into_bytes()); + pending_effects.set_tree("txid", txkey.clone(), txvalue.into_bytes()); + index_wallet_transaction( + pending_effects, + &[&transaction.unsigned_create_token.creator, &miner], + block_header_number, + **index as u32, + &txkey, + )?; Ok(binary_data) } diff --git a/src/records/record_chain/transfer_tx.rs b/src/records/record_chain/transfer_tx.rs index aac8152..6330762 100644 --- a/src/records/record_chain/transfer_tx.rs +++ b/src/records/record_chain/transfer_tx.rs @@ -3,6 +3,7 @@ use crate::common::nft_assets::nft_asset_name; use crate::decode; use crate::records::memory::mempool::BASECOIN; use crate::records::record_chain::pending_effects::{BalanceOperand, PendingEffects}; +use crate::records::record_chain::wallet_tx_index::index_wallet_transaction; use crate::sled::Db; use crate::Arc; use crate::Mutex; @@ -69,6 +70,17 @@ pub async fn process_transfer( let txkey = decode(txhash).map_err(|e| format!("Error decoding transfer txid key: {e}"))?; let txvalue = format!("{block_header_number}:{}", **index); pending_effects.set_tree("txid", txkey, txvalue.into_bytes()); + index_wallet_transaction( + pending_effects, + &[ + &transaction.unsigned_transfer.sender, + &transaction.unsigned_transfer.receiver, + &miner, + ], + block_header_number, + **index as u32, + &txhash_bytes, + )?; pending_effects.append_tree_if_key_exists( "nfts", "nft_history", diff --git a/src/records/record_chain/vanity_tx.rs b/src/records/record_chain/vanity_tx.rs index 7fb4c4b..5f54e12 100644 --- a/src/records/record_chain/vanity_tx.rs +++ b/src/records/record_chain/vanity_tx.rs @@ -2,6 +2,7 @@ use crate::blocks::vanity::VanityAddressTransaction; use crate::decode; use crate::records::memory::mempool::BASECOIN; use crate::records::record_chain::pending_effects::{BalanceOperand, PendingEffects}; +use crate::records::record_chain::wallet_tx_index::index_wallet_transaction; use crate::sled::Db; use crate::Arc; use crate::Mutex; @@ -55,7 +56,14 @@ pub async fn process_vanity( let txkey = decode(&txhash) .map_err(|err| format!("Could not decode vanity transaction hash: {err}"))?; let txvalue = format!("{block_header_number}:{}", **index); - pending_effects.set_tree("txid", txkey, txvalue.into_bytes()); + pending_effects.set_tree("txid", txkey.clone(), txvalue.into_bytes()); + index_wallet_transaction( + pending_effects, + &[owner_address, &miner], + block_header_number, + **index as u32, + &txkey, + )?; Ok(binary_data) } diff --git a/src/records/record_chain/wallet_tx_index.rs b/src/records/record_chain/wallet_tx_index.rs new file mode 100644 index 0000000..06668d0 --- /dev/null +++ b/src/records/record_chain/wallet_tx_index.rs @@ -0,0 +1,111 @@ +use crate::records::record_chain::pending_effects::PendingEffects; +use crate::sled::Db; +use crate::wallets::structures::Wallet; + +pub const WALLET_TX_INDEX_TREE: &str = "wallet_tx_index"; + +fn wallet_tx_index_key( + address: &str, + block_height: u32, + entry_index: u32, +) -> Result, String> { + let address_bytes = Wallet::short_address_to_bytes(address) + .ok_or_else(|| format!("Invalid wallet history address: {address}"))?; + let mut key = Vec::with_capacity(Wallet::SHORT_ADDRESS_BYTES_LENGTH + 8); + key.extend_from_slice(&address_bytes); + key.extend_from_slice(&block_height.to_be_bytes()); + key.extend_from_slice(&entry_index.to_be_bytes()); + Ok(key) +} + +pub fn index_wallet_transaction( + pending_effects: &mut PendingEffects, + addresses: &[&str], + block_height: u32, + entry_index: u32, + txid: &[u8], +) -> Result<(), String> { + let mut indexed_addresses: Vec> = Vec::new(); + + for address in addresses { + let address_bytes = Wallet::short_address_to_bytes(address) + .ok_or_else(|| format!("Invalid wallet history address: {address}"))?; + if indexed_addresses + .iter() + .any(|stored| stored == &address_bytes) + { + continue; + } + + indexed_addresses.push(address_bytes); + let key = wallet_tx_index_key(address, block_height, entry_index)?; + pending_effects.set_tree(WALLET_TX_INDEX_TREE, key, txid.to_vec()); + } + + Ok(()) +} + +fn txid_block_height(db: &Db, txid: &[u8]) -> Result, String> { + let tree = db + .open_tree("txid") + .map_err(|err| format!("Failed to open txid tree: {err}"))?; + let Some(value) = tree + .get(txid) + .map_err(|err| format!("Failed to read txid tree: {err}"))? + else { + return Ok(None); + }; + let value = String::from_utf8_lossy(&value); + let Some((block_height, _entry_index)) = value.split_once(':') else { + return Err("Invalid txid location value.".to_string()); + }; + block_height + .parse::() + .map(Some) + .map_err(|err| format!("Invalid txid block height: {err}")) +} + +pub fn remove_wallet_transaction_index( + db: &Db, + addresses: &[&str], + txid: &[u8], +) -> Result<(), String> { + let Some(block_height) = txid_block_height(db, txid)? else { + return Ok(()); + }; + let tree = db + .open_tree(WALLET_TX_INDEX_TREE) + .map_err(|err| format!("Failed to open wallet transaction index: {err}"))?; + let mut indexed_addresses: Vec> = Vec::new(); + + for address in addresses { + let address_bytes = Wallet::short_address_to_bytes(address) + .ok_or_else(|| format!("Invalid wallet history address: {address}"))?; + if indexed_addresses + .iter() + .any(|stored| stored == &address_bytes) + { + continue; + } + indexed_addresses.push(address_bytes.clone()); + + let mut prefix = Vec::with_capacity(Wallet::SHORT_ADDRESS_BYTES_LENGTH + 4); + prefix.extend_from_slice(&address_bytes); + prefix.extend_from_slice(&block_height.to_be_bytes()); + + let keys = tree + .scan_prefix(prefix) + .filter_map(|entry| match entry { + Ok((key, value)) if value.as_ref() == txid => Some(key.to_vec()), + _ => None, + }) + .collect::>(); + + for key in keys { + tree.remove(key) + .map_err(|err| format!("Failed to remove wallet transaction index: {err}"))?; + } + } + + Ok(()) +} diff --git a/src/rpc/client/handshake_processing.rs b/src/rpc/client/handshake_processing.rs index 09e9ed2..531db61 100644 --- a/src/rpc/client/handshake_processing.rs +++ b/src/rpc/client/handshake_processing.rs @@ -36,9 +36,9 @@ use crate::rpc::responses::RpcResponse; use crate::rpc::server::connection_memory_manager::remove_key_from_memory; use crate::rpc::server::rpc_command_loop::start_loop; use crate::sled::Db; +use crate::sleep; use crate::startup::network_broadcast::announce_self_to_network; use crate::startup::remote_height::request_remote_height; -use crate::sleep; use crate::timeout; use crate::wallets::structures::Wallet; use crate::Arc; diff --git a/src/rpc/client/register_wallet.rs b/src/rpc/client/register_wallet.rs index b115c77..5eccfff 100644 --- a/src/rpc/client/register_wallet.rs +++ b/src/rpc/client/register_wallet.rs @@ -65,9 +65,9 @@ pub async fn register_connected_wallet( let response_text = String::from_utf8_lossy(&response); match response_text.trim() { "1" => match register_short_address(db, &short_address_bytes, &public_key_bytes) { - Ok(WalletRegistrationResult::Inserted | WalletRegistrationResult::AlreadyRegistered) => { - Ok(()) - } + Ok( + WalletRegistrationResult::Inserted | WalletRegistrationResult::AlreadyRegistered, + ) => Ok(()), Ok(WalletRegistrationResult::Conflict) => { Err("Local wallet registry conflicts with startup wallet".to_string()) } diff --git a/src/rpc/command_maps.rs b/src/rpc/command_maps.rs index b84d15c..045f525 100644 --- a/src/rpc/command_maps.rs +++ b/src/rpc/command_maps.rs @@ -54,7 +54,10 @@ pub const RPC_WALLET_REGISTRY_SYNC: u8 = 39; pub const RPC_VANITY_LOOKUP: u8 = 40; pub const RPC_TORRENT_CANDIDATES: u8 = 41; pub const RPC_BLOCK_HASH_AT_HEIGHT: u8 = 42; +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_REPLY: u8 = 255; pub const MAX_RPC_REPLY_BYTES: usize = 64 * 1024 * 1024; diff --git a/src/rpc/commands/add_network_node.rs b/src/rpc/commands/add_network_node.rs index a102bb8..86df7f2 100644 --- a/src/rpc/commands/add_network_node.rs +++ b/src/rpc/commands/add_network_node.rs @@ -48,8 +48,8 @@ pub async fn add_network_node( read_bytes_from_stream::read_signature_from_stream(connections_key, stream_locked.clone()) .await?; let monitor_count = - read_bytes_from_stream::read_u16_from_stream(connections_key, stream_locked.clone()) - .await? as usize; + read_bytes_from_stream::read_u16_from_stream(connections_key, stream_locked.clone()).await? + as usize; let mut monitors = Vec::with_capacity(monitor_count); for _ in 0..monitor_count { let monitor_bytes = read_bytes_from_stream::read_short_address_from_stream( diff --git a/src/rpc/commands/address_history.rs b/src/rpc/commands/address_history.rs new file mode 100644 index 0000000..69a9e4f --- /dev/null +++ b/src/rpc/commands/address_history.rs @@ -0,0 +1,81 @@ +use crate::records::record_chain::wallet_tx_index::WALLET_TX_INDEX_TREE; +use crate::rpc::responses::RpcResponse; +use crate::sled::Db; +use crate::wallets::structures::Wallet; +use std::ops::Bound; + +const TXID_LENGTH: usize = 32; +const MAX_ADDRESS_HISTORY_TXIDS: usize = 10_000; + +fn prefix_successor(prefix: &[u8]) -> Option> { + let mut next = prefix.to_vec(); + for index in (0..next.len()).rev() { + if next[index] != u8::MAX { + next[index] += 1; + next.truncate(index + 1); + return Some(next); + } + } + None +} + +pub async fn lookup(address_bytes: Vec, skip: u32, limit: u32, db: &Db) -> RpcResponse { + if address_bytes.len() != Wallet::SHORT_ADDRESS_BYTES_LENGTH { + return RpcResponse::Binary(b"error: Invalid wallet address bytes".to_vec()); + } + + let limit = (limit as usize).min(MAX_ADDRESS_HISTORY_TXIDS); + if limit == 0 { + return RpcResponse::Binary(Vec::new()); + } + + let tree = match db.open_tree(WALLET_TX_INDEX_TREE) { + Ok(tree) => tree, + Err(err) => { + return RpcResponse::Binary( + format!("error: Failed to open wallet transaction index: {err}").into_bytes(), + ); + } + }; + + let start = Bound::Included(address_bytes.clone()); + let end = prefix_successor(&address_bytes) + .map(Bound::Excluded) + .unwrap_or(Bound::Unbounded); + + let mut skipped = 0usize; + let mut found = 0usize; + let mut txids = Vec::with_capacity(limit * TXID_LENGTH); + + for entry in tree.range((start, end)).rev() { + let (_key, value) = match entry { + Ok(entry) => entry, + Err(err) => { + return RpcResponse::Binary( + format!("error: Failed to read wallet transaction index: {err}").into_bytes(), + ); + } + }; + + if value.len() != TXID_LENGTH { + continue; + } + + if skipped < skip as usize { + skipped += 1; + continue; + } + + txids.extend_from_slice(&value); + found += 1; + if found >= limit { + break; + } + } + + RpcResponse::Binary(txids) +} + +pub async fn latest(address_bytes: Vec, limit: u32, db: &Db) -> RpcResponse { + lookup(address_bytes, 0, limit, db).await +} diff --git a/src/rpc/commands/mod.rs b/src/rpc/commands/mod.rs index 3c9d1aa..1b27ab9 100644 --- a/src/rpc/commands/mod.rs +++ b/src/rpc/commands/mod.rs @@ -2,6 +2,7 @@ pub mod add_network_node; pub mod address_coin_lookup; pub mod address_complete_balance_sheet; +pub mod address_history; pub mod bad_rpc_call; pub mod block_by_hash; pub mod block_by_height; @@ -41,6 +42,7 @@ pub mod validate_address; pub mod validate_message; pub mod validate_torrent; pub mod wallet_register; +pub mod wallet_registration_status; pub mod wallet_registry_sync; pub mod wallet_vanity_lookup; pub mod wallet_vanity_owner_lookup; diff --git a/src/rpc/commands/random_node.rs b/src/rpc/commands/random_node.rs index 03d73aa..73a241d 100644 --- a/src/rpc/commands/random_node.rs +++ b/src/rpc/commands/random_node.rs @@ -1,6 +1,6 @@ +use crate::records::block_height::get_block_height::get_height; use crate::records::memory::connections::CONNECTIONS; use crate::records::memory::network_mapping::NodeInfo; -use crate::records::block_height::get_block_height::get_height; use crate::rpc::responses::RpcResponse; use crate::sled::Db; diff --git a/src/rpc/commands/tx_submit.rs b/src/rpc/commands/tx_submit.rs index fd73595..790aacf 100644 --- a/src/rpc/commands/tx_submit.rs +++ b/src/rpc/commands/tx_submit.rs @@ -15,9 +15,7 @@ use crate::common::types::{ }; use crate::records::memory::connections::get_client_type_from_memory; use crate::records::memory::enums::ClientType; -use crate::records::memory::response_channels::{ - reserve_transient_entry_with_context, Command, -}; +use crate::records::memory::response_channels::{reserve_transient_entry_with_context, Command}; use crate::rpc::command_maps::RPC_SUBMIT_TRANSACTION; use crate::rpc::responses::RpcResponse; use crate::sled::Db; diff --git a/src/rpc/commands/wallet_registration_status.rs b/src/rpc/commands/wallet_registration_status.rs new file mode 100644 index 0000000..d7a8b51 --- /dev/null +++ b/src/rpc/commands/wallet_registration_status.rs @@ -0,0 +1,18 @@ +use crate::records::wallet_registry::short_address_exists; +use crate::rpc::responses::RpcResponse; +use crate::sled::Db; +use crate::wallets::structures::Wallet; + +pub async fn lookup(short_address_bytes: Vec, db: &Db) -> RpcResponse { + if short_address_bytes.len() != Wallet::SHORT_ADDRESS_BYTES_LENGTH { + return RpcResponse::Binary(b"0".to_vec()); + } + + match short_address_exists(db, &short_address_bytes) { + Ok(true) => RpcResponse::Binary(b"1".to_vec()), + Ok(false) => RpcResponse::Binary(b"0".to_vec()), + Err(err) => RpcResponse::Binary( + format!("error: Failed to lookup wallet registration status: {err}").into_bytes(), + ), + } +} diff --git a/src/rpc/server/rpc_command_loop.rs b/src/rpc/server/rpc_command_loop.rs index 6d05978..dfb1e3a 100644 --- a/src/rpc/server/rpc_command_loop.rs +++ b/src/rpc/server/rpc_command_loop.rs @@ -817,6 +817,53 @@ pub async fn start_loop( .send(&stream_locked, Some(&connections_key), uid) .await; } + 43 => { + // request whether a canonical short wallet address is registered + 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 result = commands::wallet_registration_status::lookup(short_address, &db).await; + result + .send(&stream_locked, Some(&connections_key), uid) + .await; + } + 44 => { + // request confirmed transaction ids indexed to a wallet address + 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 skip = read_bytes_from_stream::read_u32_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::lookup(short_address, skip, limit, &db).await; + result + .send(&stream_locked, Some(&connections_key), uid) + .await; + } 45 => { // request the canonical short address that owns a registered vanity address let (uid, _) = read_bytes_from_stream::read_uid_from_stream( @@ -850,6 +897,29 @@ pub async fn start_loop( .send(&stream_locked, Some(&connections_key), uid) .await; } + 46 => { + // request the latest confirmed transaction ids for a wallet address + 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(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 d1d3c59..5f19695 100644 --- a/src/standalone_tools/connections/sending_request.rs +++ b/src/standalone_tools/connections/sending_request.rs @@ -3,12 +3,13 @@ use crate::decode; use crate::io; use crate::records::memory::response_channels::Byte3; use crate::rpc::command_maps::{ - MAX_RPC_REPLY_BYTES, 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_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, + 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_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, }; use crate::standalone_tools::transaction_creator::create_transaction_request; use crate::timeout; @@ -461,6 +462,51 @@ async fn build_request_bytes( bin_msg.extend_from_slice(&address_bytes); } // Lookup canonical short owner address by registered vanity address. + 43 => { + let command_number: u8 = RPC_WALLET_REGISTRATION_STATUS; + let address_bytes = Wallet::normalize_to_short_address(&command_input) + .and_then(|short| Wallet::short_address_to_bytes(&short)) + .ok_or_else(|| { + io::Error::new(io::ErrorKind::InvalidInput, "Invalid wallet address") + })?; + bin_msg.extend_from_slice(&command_number.to_le_bytes()); + bin_msg.extend_from_slice(&hashmap_key); + bin_msg.extend_from_slice(&address_bytes); + } + // Lookup canonical short owner address by registered vanity address. + 44 => { + let command_number: u8 = RPC_ADDRESS_HISTORY; + let (address, rest) = command_input.split_once('|').ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidInput, + "Address history input must be address|skip|limit", + ) + })?; + let (skip, limit) = rest.split_once('|').ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidInput, + "Address history input must be address|skip|limit", + ) + })?; + let address_bytes = Wallet::normalize_to_short_address(address) + .and_then(|short| Wallet::short_address_to_bytes(&short)) + .ok_or_else(|| { + io::Error::new(io::ErrorKind::InvalidInput, "Invalid wallet address") + })?; + let skip = skip + .parse::() + .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "Invalid history skip"))?; + 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(&skip.to_le_bytes()); + bin_msg.extend_from_slice(&limit.to_le_bytes()); + } + // Lookup canonical short owner address by registered vanity address. 45 => { let command_number: u8 = RPC_VANITY_OWNER_LOOKUP; let address_bytes = Wallet::normalize_to_short_address(&command_input) @@ -472,6 +518,29 @@ async fn build_request_bytes( bin_msg.extend_from_slice(&hashmap_key); bin_msg.extend_from_slice(&address_bytes); } + // Lookup newest confirmed transaction ids tied to an address. + 46 => { + let command_number: u8 = RPC_LATEST_ADDRESS_HISTORY; + let (address, limit) = command_input.split_once('|').ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidInput, + "Latest address history input must be address|limit", + ) + })?; + let address_bytes = Wallet::normalize_to_short_address(address) + .and_then(|short| Wallet::short_address_to_bytes(&short)) + .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, diff --git a/src/startup/network_broadcast.rs b/src/startup/network_broadcast.rs index d2991e1..69b5fe2 100644 --- a/src/startup/network_broadcast.rs +++ b/src/startup/network_broadcast.rs @@ -1,11 +1,10 @@ use crate::common::binary_conversions::{binary_to_ip, binary_to_string, ip_to_binary}; -use crate::log::warn; use crate::common::network_startup::get_ip_and_port; +use crate::log::warn; use crate::records::memory::network_mapping::structs::{ - SignedNodeEdit, NODE_ADDED_BY_OFFSET, NODE_ADDED_SIGNATURE_OFFSET, - NODE_ADDED_TIMESTAMP_OFFSET, NODE_BLOCKS_MINED_OFFSET, NODE_DELETED_BLOCK_OFFSET, - NODE_DELETED_TIMESTAMP_OFFSET, NODE_IP_OFFSET, NODE_MONITOR_COUNT_OFFSET, - NODE_RECORD_FIXED_BYTES, + SignedNodeEdit, NODE_ADDED_BY_OFFSET, NODE_ADDED_SIGNATURE_OFFSET, NODE_ADDED_TIMESTAMP_OFFSET, + NODE_BLOCKS_MINED_OFFSET, NODE_DELETED_BLOCK_OFFSET, NODE_DELETED_TIMESTAMP_OFFSET, + NODE_IP_OFFSET, NODE_MONITOR_COUNT_OFFSET, NODE_RECORD_FIXED_BYTES, }; use crate::records::memory::network_mapping::NodeInfo; use crate::records::memory::response_channels::{reserve_entry, Command}; diff --git a/src/startup/remote_height.rs b/src/startup/remote_height.rs index a3e0908..edf6614 100644 --- a/src/startup/remote_height.rs +++ b/src/startup/remote_height.rs @@ -13,9 +13,12 @@ pub async fn request_remote_height( ) -> Result { // request the remote node's current chain height using // the standard reply-channel request/response flow - let (hashmap_key, _tx, rx) = - reserve_entry_with_context(map.clone(), Some(RPC_BLOCK_HEIGHT), Some(connections_key.clone())) - .await; + let (hashmap_key, _tx, rx) = reserve_entry_with_context( + map.clone(), + Some(RPC_BLOCK_HEIGHT), + Some(connections_key.clone()), + ) + .await; // message format is the height command plus the unique // reply key used to route the response back here