diff --git a/applications/minotari_node/src/http/handler/get_utxos_deleted_info.rs b/applications/minotari_node/src/http/handler/get_utxos_deleted_info.rs index 546070059f..3b70e36fb9 100644 --- a/applications/minotari_node/src/http/handler/get_utxos_deleted_info.rs +++ b/applications/minotari_node/src/http/handler/get_utxos_deleted_info.rs @@ -17,7 +17,11 @@ use tari_core::{ base_node::rpc::{BaseNodeWalletQueryService, query_service}, chain_storage::BlockchainBackend, }; -use tari_transaction_components::rpc::models::{GetUtxosDeletedInfoRequest, GetUtxosDeletedInfoResponse}; +use tari_transaction_components::rpc::models::{ + GetUtxosDeletedInfoRequest, + GetUtxosDeletedInfoResponse, + GetUtxosDeletedInfoResponseV1, +}; use tari_utilities::hex::Hex; use tonic::service::AxumBody; @@ -42,6 +46,8 @@ pub struct GetUtxosDeletedInfoParams { pub hashes: Vec>, #[serde(deserialize_with = "from_hex")] pub must_include_header: Vec, + #[serde(default)] + pub version: u8, } impl From for GetUtxosDeletedInfoRequest { @@ -59,7 +65,8 @@ impl From for GetUtxosDeletedInfoRequest { params(GetUtxosDeletedInfoParams), path = "/get_utxos_deleted_info", responses( - (status = 200, description = "UTXOs Deleted Info", body = GetUtxosDeletedInfoResponse), + (status = 200, description = "UTXOs Deleted Info (v0)", body = GetUtxosDeletedInfoResponse), + (status = 200, description = "UTXOs Deleted Info (v1, includes spent_timestamp)", body = GetUtxosDeletedInfoResponseV1), ), )] pub async fn handle( @@ -68,15 +75,25 @@ pub async fn handle( Extension(cache_cfg): Extension>, ) -> Result, (StatusCode, Json)> { debug!(target: LOG_TARGET, "Received get_utxos_deleted_info request: {params}"); + let version = params.version; let request = params.into(); - let response = query_service - .get_utxos_deleted_info(request) - .await - .map_err(error_handler_with_message)?; - - let body = Json(response); - let mut response = body.into_response(); + let mut response = match version { + 0 => { + let result = query_service + .get_utxos_deleted_info(request) + .await + .map_err(error_handler_with_message)?; + Json(result).into_response() + }, + _ => { + let result = query_service + .get_utxos_deleted_info_v1(request) + .await + .map_err(error_handler_with_message)?; + Json(result).into_response() + }, + }; apply_cache_control(response.headers_mut(), &cache_cfg, RouteKey::GetUtxosDeletedInfo, 0, 0); Ok(response) } @@ -85,14 +102,15 @@ impl Display for GetUtxosDeletedInfoParams { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, - "GetUtxosDeletedInfoParams {{ must_include_header: {}, hashes: {:?} }}", + "GetUtxosDeletedInfoParams {{ must_include_header: {}, hashes: {:?}, version: {} }}", HashOutput::try_from(self.must_include_header.as_slice()) .unwrap_or_default() .to_hex(), self.hashes .iter() .map(|h| HashOutput::try_from(h.as_slice()).unwrap_or_default().to_hex()) - .collect::>() + .collect::>(), + self.version ) } } diff --git a/base_layer/core/src/base_node/rpc/mod.rs b/base_layer/core/src/base_node/rpc/mod.rs index 97ad06182f..16db662282 100644 --- a/base_layer/core/src/base_node/rpc/mod.rs +++ b/base_layer/core/src/base_node/rpc/mod.rs @@ -89,6 +89,11 @@ pub trait BaseNodeWalletQueryService: Send + Sync + 'static { request: models::GetUtxosDeletedInfoRequest, ) -> Result; + async fn get_utxos_deleted_info_v1( + &self, + request: models::GetUtxosDeletedInfoRequest, + ) -> Result; + async fn generate_kernel_merkle_proof( &self, excess_sig: CompressedSignature, diff --git a/base_layer/core/src/base_node/rpc/query_service.rs b/base_layer/core/src/base_node/rpc/query_service.rs index f5303b6fd7..533849a5fa 100644 --- a/base_layer/core/src/base_node/rpc/query_service.rs +++ b/base_layer/core/src/base_node/rpc/query_service.rs @@ -608,6 +608,55 @@ impl BaseNodeWalletQueryService for Service { }) } + async fn get_utxos_deleted_info_v1( + &self, + request: models::GetUtxosDeletedInfoRequest, + ) -> Result { + request.validate()?; + + let mut utxos = Vec::with_capacity(request.hashes.len()); + + let must_include_header = request.must_include_header.clone().try_into()?; + if self + .db() + .fetch_header_by_block_hash(must_include_header) + .await? + .is_none() + { + return Err(Error::HeaderHashNotFound); + } + + let tip_header = self.db().fetch_tip_header().await?; + for hash in request.hashes { + let hash = hash.try_into()?; + let output = self.db().fetch_output(hash).await?; + + let utxo_info = if let Some(output) = output { + let input = self.db().fetch_input(hash).await?; + models::DeletedUtxoInfoV1 { + utxo_hash: hash.to_vec(), + found_in_header: Some((output.mined_height, output.header_hash.to_vec())), + spent_in_header: input.as_ref().map(|i| (i.spent_height, i.header_hash.to_vec())), + spent_timestamp: input.as_ref().map(|i| i.spent_timestamp), + } + } else { + models::DeletedUtxoInfoV1 { + utxo_hash: hash.to_vec(), + found_in_header: None, + spent_in_header: None, + spent_timestamp: None, + } + }; + utxos.push(utxo_info); + } + + Ok(models::GetUtxosDeletedInfoResponseV1 { + utxos, + best_block_hash: tip_header.hash().to_vec(), + best_block_height: tip_header.height(), + }) + } + async fn generate_kernel_merkle_proof( &self, excess_sig: types::CompressedSignature, diff --git a/base_layer/transaction_components/src/rpc/models/get_utxos_deleted_info.rs b/base_layer/transaction_components/src/rpc/models/get_utxos_deleted_info.rs index f796af03ed..71b358d683 100644 --- a/base_layer/transaction_components/src/rpc/models/get_utxos_deleted_info.rs +++ b/base_layer/transaction_components/src/rpc/models/get_utxos_deleted_info.rs @@ -23,3 +23,18 @@ pub struct DeletedUtxoInfo { pub found_in_header: Option<(u64, Vec)>, pub spent_in_header: Option<(u64, Vec)>, } + +#[derive(Serialize, Deserialize, ToSchema, Debug, Clone)] +pub struct GetUtxosDeletedInfoResponseV1 { + pub utxos: Vec, + pub best_block_hash: Vec, + pub best_block_height: u64, +} + +#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)] +pub struct DeletedUtxoInfoV1 { + pub utxo_hash: Vec, + pub found_in_header: Option<(u64, Vec)>, + pub spent_in_header: Option<(u64, Vec)>, + pub spent_timestamp: Option, +}