diff --git a/api/src/handlers.rs b/api/src/handlers.rs index 61e740629..b64c7c5a7 100644 --- a/api/src/handlers.rs +++ b/api/src/handlers.rs @@ -26,6 +26,7 @@ use self::blocks_api::HeaderHandler; use self::chain_api::ChainCompactHandler; use self::chain_api::ChainHandler; use self::chain_api::ChainValidationHandler; +use self::chain_api::KernelHandler; use self::chain_api::OutputHandler; use self::peers_api::PeerHandler; use self::peers_api::PeersAllHandler; @@ -119,7 +120,9 @@ pub fn build_router( let output_handler = OutputHandler { chain: Arc::downgrade(&chain), }; - + let kernel_handler = KernelHandler { + chain: Arc::downgrade(&chain), + }; let block_handler = BlockHandler { chain: Arc::downgrade(&chain), }; @@ -171,6 +174,7 @@ pub fn build_router( router.add_route("/v1/headers/*", Arc::new(header_handler))?; router.add_route("/v1/chain", Arc::new(chain_tip_handler))?; router.add_route("/v1/chain/outputs/*", Arc::new(output_handler))?; + router.add_route("/v1/chain/kernels/*", Arc::new(kernel_handler))?; router.add_route("/v1/chain/compact", Arc::new(chain_compact_handler))?; router.add_route("/v1/chain/validate", Arc::new(chain_validation_handler))?; router.add_route("/v1/txhashset/*", Arc::new(txhashset_handler))?; diff --git a/api/src/handlers/chain_api.rs b/api/src/handlers/chain_api.rs index 963bd6a6e..0fb7cc325 100644 --- a/api/src/handlers/chain_api.rs +++ b/api/src/handlers/chain_api.rs @@ -197,3 +197,72 @@ impl Handler for OutputHandler { } } } + +/// Kernel handler, search for a kernel by excess commitment +/// GET /v1/chain/kernels/XXX?min_height=YYY&max_height=ZZZ +/// The `min_height` and `max_height` parameters are optional +pub struct KernelHandler { + pub chain: Weak, +} + +impl KernelHandler { + fn get_kernel(&self, req: Request) -> Result, Error> { + let excess = req + .uri() + .path() + .trim_end_matches('/') + .rsplit('/') + .next() + .ok_or(ErrorKind::RequestError("missing excess".into()))?; + let excess = util::from_hex(excess.to_owned()) + .map_err(|_| ErrorKind::RequestError("invalid excess hex".into()))?; + if excess.len() != 33 { + return Err(ErrorKind::RequestError("invalid excess length".into()).into()); + } + let excess = Commitment::from_vec(excess); + + let chain = w(&self.chain)?; + + let mut min_height: Option = None; + let mut max_height: Option = None; + + // Check query parameters for minimum and maximum search height + if let Some(q) = req.uri().query() { + let params = QueryParams::from(q); + if let Some(h) = params.get("min_height") { + let h = h + .parse() + .map_err(|_| ErrorKind::RequestError("invalid minimum height".into()))?; + // Default is genesis + min_height = if h == 0 { None } else { Some(h) }; + } + if let Some(h) = params.get("max_height") { + let h = h + .parse() + .map_err(|_| ErrorKind::RequestError("invalid maximum height".into()))?; + // Default is current head + let head_height = chain + .head() + .map_err(|e| ErrorKind::Internal(format!("{}", e)))? + .height; + max_height = if h >= head_height { None } else { Some(h) }; + } + } + + let kernel = chain + .get_kernel_height(&excess, min_height, max_height) + .map_err(|e| ErrorKind::Internal(format!("{}", e)))? + .map(|(tx_kernel, height, mmr_index)| LocatedTxKernel { + tx_kernel, + height, + mmr_index, + }); + Ok(kernel) + } +} + +impl Handler for KernelHandler { + fn get(&self, req: Request) -> ResponseFuture { + result_to_response(self.get_kernel(req)) + } +} diff --git a/api/src/types.rs b/api/src/types.rs index 52b90fb44..64e95d1cc 100644 --- a/api/src/types.rs +++ b/api/src/types.rs @@ -17,7 +17,7 @@ use std::sync::Arc; use crate::chain; use crate::core::core::hash::Hashed; use crate::core::core::merkle_proof::MerkleProof; -use crate::core::core::KernelFeatures; +use crate::core::core::{KernelFeatures, TxKernel}; use crate::core::{core, ser}; use crate::p2p; use crate::util; @@ -699,6 +699,13 @@ pub struct OutputListing { pub outputs: Vec, } +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct LocatedTxKernel { + pub tx_kernel: TxKernel, + pub height: u64, + pub mmr_index: u64, +} + #[derive(Serialize, Deserialize)] pub struct PoolInfo { /// Size of the pool diff --git a/chain/src/chain.rs b/chain/src/chain.rs index 879fcdb48..a1dd5dab8 100644 --- a/chain/src/chain.rs +++ b/chain/src/chain.rs @@ -19,7 +19,8 @@ use crate::core::core::hash::{Hash, Hashed, ZERO_HASH}; use crate::core::core::merkle_proof::MerkleProof; use crate::core::core::verifier_cache::VerifierCache; use crate::core::core::{ - Block, BlockHeader, BlockSums, Committed, Output, OutputIdentifier, Transaction, TxKernelEntry, + Block, BlockHeader, BlockSums, Committed, Output, OutputIdentifier, Transaction, TxKernel, + TxKernelEntry, }; use crate::core::global; use crate::core::pow; @@ -1281,6 +1282,72 @@ impl Chain { Ok(txhashset.get_header_by_height(output_pos.height)?) } + /// Gets the kernel with a given excess and the block height it is included in. + pub fn get_kernel_height( + &self, + excess: &Commitment, + min_height: Option, + max_height: Option, + ) -> Result, Error> { + let min_index = match min_height { + Some(h) => Some(self.get_header_by_height(h - 1)?.kernel_mmr_size + 1), + None => None, + }; + + let max_index = match max_height { + Some(h) => Some(self.get_header_by_height(h)?.kernel_mmr_size), + None => None, + }; + + let (kernel, mmr_index) = match self + .txhashset + .read() + .find_kernel(&excess, min_index, max_index) + { + Some(k) => k, + None => return Ok(None), + }; + + let header = self.get_header_for_kernel_index(mmr_index, min_height, max_height)?; + + Ok(Some((kernel, header.height, mmr_index))) + } + + /// Gets the block header in which a given kernel mmr index appears in the txhashset. + pub fn get_header_for_kernel_index( + &self, + kernel_mmr_index: u64, + min_height: Option, + max_height: Option, + ) -> Result { + let txhashset = self.txhashset.read(); + + let mut min = min_height.unwrap_or(0).saturating_sub(1); + let mut max = match max_height { + Some(h) => h, + None => self.head()?.height, + }; + + loop { + let search_height = max - (max - min) / 2; + let h = txhashset.get_header_by_height(search_height)?; + if search_height == 0 { + return Ok(h); + } + let h_prev = txhashset.get_header_by_height(search_height - 1)?; + if kernel_mmr_index > h.kernel_mmr_size { + min = search_height; + } else if kernel_mmr_index < h_prev.kernel_mmr_size { + max = search_height; + } else { + if kernel_mmr_index == h_prev.kernel_mmr_size { + return Ok(h_prev); + } + return Ok(h); + } + } + } + /// Verifies the given block header is actually on the current chain. /// Checks the header_by_height index to verify the header is where we say /// it is diff --git a/chain/src/txhashset/txhashset.rs b/chain/src/txhashset/txhashset.rs index 95955ebff..fe969415b 100644 --- a/chain/src/txhashset/txhashset.rs +++ b/chain/src/txhashset/txhashset.rs @@ -269,6 +269,29 @@ impl TxHashSet { .elements_from_insertion_index(start_index, max_count) } + /// Find a kernel with a given excess. Work backwards from `max_index` to `min_index` + pub fn find_kernel( + &self, + excess: &Commitment, + min_index: Option, + max_index: Option, + ) -> Option<(TxKernel, u64)> { + let min_index = min_index.unwrap_or(1); + let max_index = max_index.unwrap_or(self.kernel_pmmr_h.last_pos); + + let pmmr = ReadonlyPMMR::at(&self.kernel_pmmr_h.backend, self.kernel_pmmr_h.last_pos); + let mut index = max_index + 1; + while index > min_index { + index -= 1; + if let Some(t) = pmmr.get_data(index) { + if &t.kernel.excess == excess { + return Some((t.kernel, index)); + } + } + } + None + } + /// Get MMR roots. pub fn roots(&self) -> TxHashSetRoots { let header_pmmr =