diff --git a/src/new_index/mempool.rs b/src/new_index/mempool.rs index 188c93cd..6d867c08 100644 --- a/src/new_index/mempool.rs +++ b/src/new_index/mempool.rs @@ -133,18 +133,45 @@ impl Mempool { .any(|txin| self.txstore.contains_key(&txin.previous_output.txid)) } - pub fn history(&self, scripthash: &[u8], limit: usize) -> Vec { + pub fn history( + &self, + scripthash: &[u8], + last_seen_txid: Option<&Txid>, + limit: usize, + ) -> Vec { let _timer = self.latency.with_label_values(&["history"]).start_timer(); + self.history.get(scripthash).map_or_else( + || vec![], + |entries| self._history(entries, last_seen_txid, limit), + ) + } + + pub fn history_txids_iter<'a>(&'a self, scripthash: &[u8]) -> impl Iterator + 'a { self.history .get(scripthash) - .map_or_else(|| vec![], |entries| self._history(entries, limit)) + .into_iter() + .flat_map(|v| v.iter().map(|e| e.get_txid()).unique()) } - fn _history(&self, entries: &[TxHistoryInfo], limit: usize) -> Vec { + fn _history( + &self, + entries: &[TxHistoryInfo], + last_seen_txid: Option<&Txid>, + limit: usize, + ) -> Vec { entries .iter() .map(|e| e.get_txid()) .unique() + // TODO seek directly to last seen tx without reading earlier rows + .skip_while(|txid| { + // skip until we reach the last_seen_txid + last_seen_txid.map_or(false, |last_seen_txid| last_seen_txid != txid) + }) + .skip(match last_seen_txid { + Some(_) => 1, // skip the last_seen_txid itself + None => 0, + }) .take(limit) .map(|txid| self.txstore.get(&txid).expect("missing mempool tx")) .cloned() @@ -515,7 +542,7 @@ impl Mempool { .start_timer(); self.asset_history .get(asset_id) - .map_or_else(|| vec![], |entries| self._history(entries, limit)) + .map_or_else(|| vec![], |entries| self._history(entries, None, limit)) } } diff --git a/src/new_index/schema.rs b/src/new_index/schema.rs index 35e49f8d..f89860d0 100644 --- a/src/new_index/schema.rs +++ b/src/new_index/schema.rs @@ -468,6 +468,12 @@ impl ChainQuery { self._history(b'H', scripthash, last_seen_txid, limit) } + pub fn history_txids_iter<'a>(&'a self, scripthash: &[u8]) -> impl Iterator + 'a { + self.history_iter_scan_reverse(b'H', scripthash) + .map(|row| TxHistoryRow::from_row(row).get_txid()) + .unique() + } + fn _history( &self, code: u8, diff --git a/src/rest.rs b/src/rest.rs index 70c353d8..b43fde32 100644 --- a/src/rest.rs +++ b/src/rest.rs @@ -793,24 +793,61 @@ fn handle_request( None, ) => { let script_hash = to_scripthash(script_type, script_str, config.network_type)?; + let max_txs = query_params + .get("max_txs") + .and_then(|s| s.parse::().ok()) + .unwrap_or(CHAIN_TXS_PER_PAGE); + let after_txid = query_params + .get("after_txid") + .and_then(|s| s.parse::().ok()); let mut txs = vec![]; + if let Some(given_txid) = &after_txid { + let is_mempool = query + .mempool() + .history_txids_iter(&script_hash[..]) + .any(|txid| given_txid == &txid); + let is_confirmed = if is_mempool { + false + } else { + query + .chain() + .history_txids_iter(&script_hash[..]) + .any(|txid| given_txid == &txid) + }; + if !is_mempool && !is_confirmed { + return Err(HttpError( + StatusCode::UNPROCESSABLE_ENTITY, + String::from("after_txid not found"), + )); + } + } + txs.extend( query .mempool() - .history(&script_hash[..], MAX_MEMPOOL_TXS) + .history(&script_hash[..], after_txid.as_ref(), max_txs) .into_iter() .map(|tx| (tx, None)), ); - txs.extend( - query - .chain() - .history(&script_hash[..], None, CHAIN_TXS_PER_PAGE) - .into_iter() - .map(|(tx, blockid)| (tx, Some(blockid))), - ); + if txs.len() < max_txs { + let after_txid_ref = if !txs.is_empty() { + // If there are any txs, we know mempool found the + // after_txid IF it exists... so always return None. + None + } else { + after_txid.as_ref() + }; + txs.extend( + query + .chain() + .history(&script_hash[..], after_txid_ref, max_txs - txs.len()) + .into_iter() + .map(|(tx, blockid)| (tx, Some(blockid))), + ); + } json_maybe_error_response(prepare_txs(txs, query, config), TTL_SHORT) } @@ -833,14 +870,14 @@ fn handle_request( ) => { let script_hash = to_scripthash(script_type, script_str, config.network_type)?; let last_seen_txid = last_seen_txid.and_then(|txid| Txid::from_hex(txid).ok()); + let max_txs = query_params + .get("max_txs") + .and_then(|s| s.parse::().ok()) + .unwrap_or(CHAIN_TXS_PER_PAGE); let txs = query .chain() - .history( - &script_hash[..], - last_seen_txid.as_ref(), - CHAIN_TXS_PER_PAGE, - ) + .history(&script_hash[..], last_seen_txid.as_ref(), max_txs) .into_iter() .map(|(tx, blockid)| (tx, Some(blockid))) .collect(); @@ -864,10 +901,14 @@ fn handle_request( None, ) => { let script_hash = to_scripthash(script_type, script_str, config.network_type)?; + let max_txs = query_params + .get("max_txs") + .and_then(|s| s.parse::().ok()) + .unwrap_or(MAX_MEMPOOL_TXS); let txs = query .mempool() - .history(&script_hash[..], MAX_MEMPOOL_TXS) + .history(&script_hash[..], None, max_txs) .into_iter() .map(|tx| (tx, None)) .collect();