diff --git a/components/consensusmanager/src/session.rs b/components/consensusmanager/src/session.rs index 1d90cb157a..680498d6a5 100644 --- a/components/consensusmanager/src/session.rs +++ b/components/consensusmanager/src/session.rs @@ -291,8 +291,12 @@ impl ConsensusSessionOwned { self.clone().spawn_blocking(|c| c.get_chain_block_samples()).await } - pub async fn async_get_utxo_return_address(&self, txid: Hash, accepting_block_daa_score: u64) -> Option { - self.clone().spawn_blocking(move |c| c.get_utxo_return_address(txid, accepting_block_daa_score)).await + pub async fn async_get_utxo_return_script_public_key( + &self, + txid: Hash, + accepting_block_daa_score: u64, + ) -> Option { + self.clone().spawn_blocking(move |c| c.get_utxo_return_script_public_key(txid, accepting_block_daa_score)).await } /// Returns the antipast of block `hash` from the POV of `context`, i.e. `antipast(hash) ∩ past(context)`. diff --git a/consensus/core/src/api/mod.rs b/consensus/core/src/api/mod.rs index ff3c0957ac..99d7b72f05 100644 --- a/consensus/core/src/api/mod.rs +++ b/consensus/core/src/api/mod.rs @@ -155,7 +155,7 @@ pub trait ConsensusApi: Send + Sync { unimplemented!() } - fn get_utxo_return_address(&self, txid: Hash, daa_score: u64) -> Option { + fn get_utxo_return_script_public_key(&self, txid: Hash, daa_score: u64) -> Option { unimplemented!() } diff --git a/consensus/src/consensus/mod.rs b/consensus/src/consensus/mod.rs index 0a8818cf34..73f6f07ec5 100644 --- a/consensus/src/consensus/mod.rs +++ b/consensus/src/consensus/mod.rs @@ -69,6 +69,7 @@ use crossbeam_channel::{ use itertools::Itertools; use kaspa_consensusmanager::{SessionLock, SessionReadGuard}; +use kaspa_core::{trace, warn}; use kaspa_database::prelude::StoreResultExtensions; use kaspa_hashes::Hash; use kaspa_muhash::MuHash; @@ -614,83 +615,95 @@ impl ConsensusApi for Consensus { sample_headers } - fn get_utxo_return_address(&self, txid: Hash, daa_score: u64) -> Option { + fn get_utxo_return_script_public_key(&self, txid: Hash, target_daa_score: u64) -> Option { // We need consistency between the past pruning points, selected chain and header store reads let _guard = self.pruning_lock.blocking_read(); let sc_read = self.selected_chain_store.read(); - let low = self.pruning_point_store.read().get().unwrap().pruning_point; - let high = sc_read.get_tip().unwrap().1; + let pp_hash = self.pruning_point_store.read().get().unwrap().pruning_point; + let pp_index = sc_read.get_by_hash(pp_hash).unwrap(); + let (tip_index, tip_hash) = sc_read.get_tip().unwrap(); + let tip_daa_score = self.headers_store.get_compact_header_data(tip_hash).unwrap().daa_score; - let mut low_index = sc_read.get_by_hash(low).unwrap(); - let mut high_index = sc_read.get_by_hash(high).unwrap(); + let mut low_index = tip_index.saturating_sub(tip_daa_score.saturating_sub(target_daa_score)).max(pp_index); + let mut high_index = tip_index; - // let mut locator = Vec::with_capacity((high_index - low_index).ceiling_log_base_2() as usize); - // let mut current_index = high_index; let mut matching_chain_block_hash: Option = None; while low_index <= high_index { let mid = low_index + (high_index - low_index) / 2; if let Ok(hash) = sc_read.get_by_index(mid) { if let Ok(compact_header) = self.headers_store.get_compact_header_data(hash) { - if compact_header.daa_score == daa_score { + if compact_header.daa_score == target_daa_score { // We found the chain block we need matching_chain_block_hash = Some(hash); break; - } else if compact_header.daa_score < daa_score { + } else if compact_header.daa_score < target_daa_score { low_index = mid + 1; } else { high_index = mid - 1; } } else { - println!("Did not find a compact header with hash {}", hash); - break; + trace!("Did not find a compact header with hash {}", hash); + return None; } } else { - println!("Did not find a hash at index {}", mid); - break; + trace!("Did not find a hash at index {}", mid); + return None; } } if matching_chain_block_hash.is_none() { - println!("No matching chain block hash found"); return None; } let matching_chain_block_hash = matching_chain_block_hash?; if let Ok(acceptance_data) = self.acceptance_data_store.get(matching_chain_block_hash) { - let containing_acceptance = acceptance_data - .iter() - .find(|&mbad| mbad.accepted_transactions.iter().find(|&tx| tx.transaction_id == txid).is_some()) - .cloned(); - - if let Some(containing_acceptance) = containing_acceptance { - // I found the merged block containing the TXID - // Now I need to find the txid - let tx = self - .block_transactions_store - .get(containing_acceptance.block_hash) - .unwrap() - .iter() - .find(|&tx| tx.id() == txid) - .cloned() - .unwrap(); + let maybe_index_and_containing_acceptance = + acceptance_data.iter().find_map(|mbad| { + let tx_arr_index = mbad.accepted_transactions.iter().enumerate().find_map(|(index, &ref tx)| { + if tx.transaction_id == txid { + Some(index) + } else { + None + } + }); + + if let Some(index) = tx_arr_index { + Some((index, mbad.clone())) + } else { + None + } + }); + + if let Some((index, containing_acceptance)) = maybe_index_and_containing_acceptance { + // Found Merged block containing the TXID + let tx = &self.block_transactions_store.get(containing_acceptance.block_hash).unwrap()[index]; + + if tx.id() != txid { + // Should never happen, but do a sanity check. This would mean something went wrong with storing block transactions + // Sanity check is necessary to guarantee that this function will never give back a wrong address (err on the side of None) + warn!( + "Expected {} to match {} when checking block_transaction_store using array index of transaction", + tx.id(), + txid + ); + return None; + } if tx.inputs.is_empty() { + // A transaction may have no inputs (like a coinbase transaction) return None; } - let first_input = &tx.inputs[0]; - - let prev_outpoint = &first_input.previous_outpoint; - + let first_input_prev_outpoint = &tx.inputs[0].previous_outpoint; + // Expected to never fail, since let utxo_diff = self.utxo_diffs_store.get(matching_chain_block_hash).unwrap(); - let removed_diffs = utxo_diff.removed(); - return Some(removed_diffs.get(prev_outpoint)?.script_public_key.clone()); + return Some(removed_diffs.get(first_input_prev_outpoint)?.script_public_key.clone()); } }; diff --git a/rpc/service/src/service.rs b/rpc/service/src/service.rs index 5912d36ab4..0c6a8ea2ca 100644 --- a/rpc/service/src/service.rs +++ b/rpc/service/src/service.rs @@ -57,6 +57,7 @@ use kaspa_rpc_core::{ notify::connection::ChannelConnection, Notification, RpcError, RpcResult, }; +use kaspa_txscript::opcodes::codes; use kaspa_txscript::{extract_script_pub_key_address, pay_to_address_script}; use kaspa_utils::{channel::Channel, triggers::SingleTrigger}; use kaspa_utils_tower::counters::TowerConnectionCounters; @@ -623,23 +624,24 @@ NOTE: This error usually indicates an RPC conversion error between the node and async fn get_utxo_return_address_call(&self, request: GetUtxoReturnAddressRequest) -> RpcResult { let session = self.consensus_manager.consensus().session().await; - let maybe_spk = session.async_get_utxo_return_address(request.txid, request.accepting_block_daa_score).await; - let mut return_address = None; // Convert a SPK to an Address - if let Some(spk) = maybe_spk { + if let Some(spk) = session.async_get_utxo_return_script_public_key(request.txid, request.accepting_block_daa_score).await { let script = spk.script(); - // Address scripts are only either 34 or 35 in length: - if script.len() == 34 && script[0] == 0x20 && script[33] == 0xac { + // Standard Address scripts are only either 34 or 35 in length: + if script.len() == 34 && script[0] == codes::OpData32 && script[33] == codes::OpCheckSig { + // This is a standard Schnorr Address return_address = Some(RpcAddress::new(self.config.prefix(), kaspa_addresses::Version::PubKey, &script[1..33])); } else if script.len() == 35 { // Could be ECDSA address OR P2SH - if script[0] == 0x21 && script[34] == 0xab { + if script[0] == codes::OpData33 && script[34] == codes::OpCheckSigECDSA { + // This is an standard ECDSA Address return_address = Some(RpcAddress::new(self.config.prefix(), kaspa_addresses::Version::PubKeyECDSA, &script[1..34])); - } else if script[0] == 0xaa && script[1] == 0x20 && script[34] == 0x87 { + } else if script[0] == codes::OpBlake2b && script[1] == codes::OpData32 && script[34] == codes::OpEqual { + // This is a standard P2SH Address return_address = Some(RpcAddress::new(self.config.prefix(), kaspa_addresses::Version::ScriptHash, &script[2..34])); } }