From db5b3887f9d4037525b6b228e87c0a31b04467d7 Mon Sep 17 00:00:00 2001 From: nym21 Date: Thu, 9 Apr 2026 14:58:25 +0200 Subject: [PATCH] global: speed improvement part3 --- crates/brk_query/src/impl/addr.rs | 37 +-- crates/brk_query/src/impl/block/txs.rs | 99 ++------ crates/brk_query/src/impl/tx.rs | 325 +++++++++++-------------- crates/brk_server/src/state.rs | 20 +- 4 files changed, 186 insertions(+), 295 deletions(-) diff --git a/crates/brk_query/src/impl/addr.rs b/crates/brk_query/src/impl/addr.rs index 8d9ef4916..e3085c094 100644 --- a/crates/brk_query/src/impl/addr.rs +++ b/crates/brk_query/src/impl/addr.rs @@ -39,9 +39,13 @@ impl Query { let addr_type = output_type; let hash = AddrHash::from(&bytes); - let Ok(Some(type_index)) = stores + let Some(store) = stores .addr_type_to_addr_hash_to_addr_index - .get_unwrap(addr_type) + .get(addr_type) + else { + return Err(Error::InvalidAddr); + }; + let Ok(Some(type_index)) = store .get(&hash) .map(|opt| opt.map(|cow| cow.into_owned())) else { @@ -139,12 +143,7 @@ impl Query { .data()?; if let Some(after_txid) = after_txid { - let after_tx_index = stores - .txid_prefix_to_tx_index - .get(&after_txid.into()) - .map_err(|_| Error::UnknownTxid)? - .ok_or(Error::UnknownTxid)? - .into_owned(); + let after_tx_index = self.resolve_tx_index(&after_txid)?; // Seek directly to after_tx_index and iterate backward — O(limit) let min = AddrIndexTxIndex::min_for_addr(type_index); @@ -189,28 +188,30 @@ impl Query { let txid_reader = vecs.transactions.txid.reader(); let first_txout_index_reader = vecs.transactions.first_txout_index.reader(); let value_reader = vecs.outputs.value.reader(); - let tx_heights = &self.computer().indexes.tx_heights; + let mut cached_status: Option<(Height, TxStatus)> = None; let mut utxos = Vec::with_capacity(outpoints.len()); for (tx_index, vout) in outpoints { let txid = txid_reader.get(tx_index.to_usize()); - let height: Height = tx_heights.get_shared(tx_index).data()?; let first_txout_index = first_txout_index_reader.get(tx_index.to_usize()); let value = value_reader.get(usize::from(first_txout_index + vout)); - let block_hash = vecs.blocks.cached_blockhash.collect_one(height).data()?; - let block_time = vecs.blocks.cached_timestamp.collect_one(height).data()?; + let height = self.confirmed_status_height(tx_index)?; + let status = if let Some((h, ref s)) = cached_status + && h == height + { + s.clone() + } else { + let s = self.confirmed_status_at(height)?; + cached_status = Some((height, s.clone())); + s + }; utxos.push(Utxo { txid, vout, - status: TxStatus { - confirmed: true, - block_height: Some(height), - block_hash: Some(block_hash), - block_time: Some(block_time), - }, + status, value, }); } diff --git a/crates/brk_query/src/impl/block/txs.rs b/crates/brk_query/src/impl/block/txs.rs index bb18dc6d5..acf8e0c5a 100644 --- a/crates/brk_query/src/impl/block/txs.rs +++ b/crates/brk_query/src/impl/block/txs.rs @@ -3,7 +3,7 @@ use std::io::Cursor; use bitcoin::{consensus::Decodable, hex::DisplayHex}; use brk_error::{Error, OptionData, Result}; use brk_types::{ - BlkPosition, BlockHash, Height, OutPoint, OutputType, RawLockTime, Sats, StoredU32, Timestamp, + BlkPosition, BlockHash, Height, OutPoint, OutputType, RawLockTime, Sats, StoredU32, Transaction, TxIn, TxInIndex, TxIndex, TxOut, TxStatus, Txid, TypeIndex, Vout, Weight, }; use rustc_hash::FxHashMap; @@ -75,7 +75,6 @@ impl Query { return Ok(Vec::new()); } - let t0 = std::time::Instant::now(); let len = indices.len(); // Sort positions ascending for sequential I/O (O(n) when already sorted) @@ -87,21 +86,19 @@ impl Query { // ── Phase 1: Decode all transactions, collect outpoints ───────── - let tx_heights = &self.computer().indexes.tx_heights; let mut txid_cursor = indexer.vecs.transactions.txid.cursor(); let mut locktime_cursor = indexer.vecs.transactions.raw_locktime.cursor(); let mut total_size_cursor = indexer.vecs.transactions.total_size.cursor(); let mut first_txin_cursor = indexer.vecs.transactions.first_txin_index.cursor(); let mut position_cursor = indexer.vecs.transactions.position.cursor(); + struct DecodedTx { pos: usize, tx_index: TxIndex, txid: Txid, - height: Height, lock_time: RawLockTime, total_size: StoredU32, - block_hash: BlockHash, - block_time: Timestamp, + status: TxStatus, decoded: bitcoin::Transaction, first_txin_index: TxInIndex, outpoints: Vec, @@ -109,6 +106,7 @@ impl Query { let mut decoded_txs: Vec = Vec::with_capacity(len); let mut total_inputs: usize = 0; + let mut cached_status: Option<(Height, TxStatus)> = None; // Phase 1a: Read metadata + decode transactions (no outpoint reads yet) for &pos in &order { @@ -116,24 +114,21 @@ impl Query { let idx = tx_index.to_usize(); let txid: Txid = txid_cursor.get(idx).data()?; - let height: Height = tx_heights.get_shared(tx_index).data()?; let lock_time: RawLockTime = locktime_cursor.get(idx).data()?; let total_size: StoredU32 = total_size_cursor.get(idx).data()?; let first_txin_index: TxInIndex = first_txin_cursor.get(idx).data()?; let position: BlkPosition = position_cursor.get(idx).data()?; - let block_hash = indexer - .vecs - .blocks - .cached_blockhash - .collect_one(height) - .data()?; - let block_time = indexer - .vecs - .blocks - .cached_timestamp - .collect_one(height) - .data()?; + let height = self.confirmed_status_height(tx_index)?; + let status = if let Some((h, ref s)) = cached_status + && h == height + { + s.clone() + } else { + let s = self.confirmed_status_at(height)?; + cached_status = Some((height, s.clone())); + s + }; let buffer = reader.read_raw_bytes(position, *total_size as usize)?; let decoded = bitcoin::Transaction::consensus_decode(&mut Cursor::new(buffer)) @@ -145,19 +140,15 @@ impl Query { pos, tx_index, txid, - height, lock_time, total_size, - block_hash, - block_time, + status, decoded, first_txin_index, outpoints: Vec::new(), }); } - let t_phase1a = t0.elapsed(); - // Phase 1b: Batch-read outpoints + prevout data via cursors (PcoVec — // sequential cursor avoids re-decompressing the same pages). // Reading output_type/type_index/value HERE from inputs vecs (sequential) @@ -187,41 +178,18 @@ impl Query { dtx.outpoints = outpoints; } - let t_phase1b = t0.elapsed(); - - // ── Phase 2: Batch-read prevout data in sorted order ──────────── - - // Collect all non-coinbase outpoints, deduplicate, sort by tx_index - let mut prevout_keys: Vec = Vec::with_capacity(total_inputs); - for dtx in &decoded_txs { - for &op in &dtx.outpoints { - if op.is_not_coinbase() { - prevout_keys.push(op); - } - } - } - prevout_keys.sort_unstable(); - prevout_keys.dedup(); - - // Batch-read txid sorted by prev_tx_index (only remaining random read) - let txid_reader = indexer.vecs.transactions.txid.reader(); + // ── Phase 2: Build prevout TxOut map (script_pubkey from addr vecs) ── let addr_readers = indexer.vecs.addrs.addr_readers(); - let mut prevout_map: FxHashMap = - FxHashMap::with_capacity_and_hasher(prevout_keys.len(), Default::default()); + let mut prevout_map: FxHashMap = + FxHashMap::with_capacity_and_hasher(total_inputs, Default::default()); - for &op in &prevout_keys { - let txid = txid_reader.get(op.tx_index().to_usize()); - // output_type, type_index, value pre-read from inputs vecs (sequential) - let &(output_type, type_index, value) = - prevout_input_data.get(&op).unwrap(); + for (&op, &(output_type, type_index, value)) in &prevout_input_data { let script_pubkey = addr_readers.script_pubkey(output_type, type_index); - prevout_map.insert(op, (txid, TxOut::from((script_pubkey, value)))); + prevout_map.insert(op, TxOut::from((script_pubkey, value))); } - let t_phase2 = t0.elapsed(); - // ── Phase 3: Assemble Transaction objects ─────────────────────── let mut txs: Vec> = (0..len).map(|_| None).collect(); @@ -239,8 +207,8 @@ impl Query { let (prev_txid, prev_vout, prevout) = if is_coinbase { (Txid::COINBASE, Vout::MAX, None) } else { - let (prev_txid, prev_txout) = - prevout_map.get(&outpoint).data()?.clone(); + let prev_txid = Txid::from(txin.previous_output.txid); + let prev_txout = prevout_map.get(&outpoint).data()?.clone(); (prev_txid, outpoint.vout(), Some(prev_txout)) }; @@ -299,34 +267,13 @@ impl Query { fee: Sats::ZERO, input, output, - status: TxStatus { - confirmed: true, - block_height: Some(dtx.height), - block_hash: Some(dtx.block_hash), - block_time: Some(dtx.block_time), - }, + status: dtx.status, }; transaction.compute_fee(); txs[dtx.pos] = Some(transaction); } - let t_phase3 = t0.elapsed(); - - if t_phase3.as_millis() > 50 { - eprintln!( - "[perf:txs] n={} vin={} prevouts={} | 1a={:.1?} 1b={:.1?} | 2={:.1?} | 3={:.1?} | total={:.1?}", - len, - total_inputs, - prevout_keys.len(), - t_phase1a, - t_phase1b - t_phase1a, - t_phase2 - t_phase1b, - t_phase3 - t_phase2, - t_phase3, - ); - } - Ok(txs.into_iter().map(Option::unwrap).collect()) } diff --git a/crates/brk_query/src/impl/tx.rs b/crates/brk_query/src/impl/tx.rs index f04722ab7..6fe03f6bf 100644 --- a/crates/brk_query/src/impl/tx.rs +++ b/crates/brk_query/src/impl/tx.rs @@ -1,7 +1,7 @@ use bitcoin::hex::{DisplayHex, FromHex}; use brk_error::{Error, OptionData, Result}; use brk_types::{ - Height, MerkleProof, Transaction, TxInIndex, TxIndex, TxOutIndex, + BlockHash, Height, MerkleProof, Timestamp, Transaction, TxInIndex, TxIndex, TxOutIndex, TxOutspend, TxStatus, Txid, TxidPrefix, Vin, Vout, }; use vecdb::{ReadableVec, VecIndex}; @@ -9,59 +9,50 @@ use vecdb::{ReadableVec, VecIndex}; use crate::Query; impl Query { - pub fn transaction(&self, txid: &Txid) -> Result { - // First check mempool for unconfirmed transactions - if let Some(mempool) = self.mempool() - && let Some(tx_with_hex) = mempool.get_txs().get(txid) - { - return Ok(tx_with_hex.tx().clone()); - } + // ── Txid → TxIndex resolution (single source of truth) ───────── - // Look up confirmed transaction by txid prefix - let prefix = TxidPrefix::from(txid); - let indexer = self.indexer(); - let Ok(Some(tx_index)) = indexer + /// Resolve a txid to its internal TxIndex via prefix lookup. + #[inline] + pub(crate) fn resolve_tx_index(&self, txid: &Txid) -> Result { + self.indexer() .stores .txid_prefix_to_tx_index - .get(&prefix) - .map(|opt| opt.map(|cow| cow.into_owned())) - else { - return Err(Error::UnknownTxid); - }; - - self.transaction_by_index(tx_index) + .get(&TxidPrefix::from(txid)) + .map_err(|_| Error::UnknownTxid)? + .map(|cow| cow.into_owned()) + .ok_or(Error::UnknownTxid) } - pub fn transaction_status(&self, txid: &Txid) -> Result { - // First check mempool for unconfirmed transactions - if let Some(mempool) = self.mempool() - && mempool.get_txs().contains_key(txid) - { - return Ok(TxStatus::UNCONFIRMED); - } + /// Resolve a txid to (TxIndex, Height). + pub fn resolve_tx(&self, txid: &Txid) -> Result<(TxIndex, Height)> { + let tx_index = self.resolve_tx_index(txid)?; + let height = self.confirmed_status_height(tx_index)?; + Ok((tx_index, height)) + } - // Look up confirmed transaction by txid prefix - let prefix = TxidPrefix::from(txid); - let indexer = self.indexer(); - let Ok(Some(tx_index)) = indexer - .stores - .txid_prefix_to_tx_index - .get(&prefix) - .map(|opt| opt.map(|cow| cow.into_owned())) - else { - return Err(Error::UnknownTxid); - }; + // ── TxStatus construction (single source of truth) ───────────── - // Get block info for status - let height = self - .computer() + /// Height for a confirmed tx_index via in-memory TxHeights lookup. + #[inline] + pub(crate) fn confirmed_status_height(&self, tx_index: TxIndex) -> Result { + self.computer() .indexes .tx_heights .get_shared(tx_index) - .data()?; - let block_hash = indexer.vecs.blocks.cached_blockhash.collect_one(height).data()?; - let block_time = indexer.vecs.blocks.cached_timestamp.collect_one(height).data()?; + .data() + } + /// Full confirmed TxStatus from a tx_index. + #[inline] + pub(crate) fn confirmed_status(&self, tx_index: TxIndex) -> Result { + let height = self.confirmed_status_height(tx_index)?; + self.confirmed_status_at(height) + } + + /// Full confirmed TxStatus from a known height. + #[inline] + pub(crate) fn confirmed_status_at(&self, height: Height) -> Result { + let (block_hash, block_time) = self.block_hash_and_time(height)?; Ok(TxStatus { confirmed: true, block_height: Some(height), @@ -70,6 +61,33 @@ impl Query { }) } + /// Block hash + timestamp for a height (cached vecs, fast). + #[inline] + pub(crate) fn block_hash_and_time(&self, height: Height) -> Result<(BlockHash, Timestamp)> { + let indexer = self.indexer(); + let hash = indexer.vecs.blocks.cached_blockhash.collect_one(height).data()?; + let time = indexer.vecs.blocks.cached_timestamp.collect_one(height).data()?; + Ok((hash, time)) + } + + // ── Transaction queries ──────────────────────────────────────── + + pub fn transaction(&self, txid: &Txid) -> Result { + if let Some(mempool) = self.mempool() + && let Some(tx_with_hex) = mempool.get_txs().get(txid) + { + return Ok(tx_with_hex.tx().clone()); + } + self.transaction_by_index(self.resolve_tx_index(txid)?) + } + + pub fn transaction_status(&self, txid: &Txid) -> Result { + if self.mempool().is_some_and(|m| m.get_txs().contains_key(txid)) { + return Ok(TxStatus::UNCONFIRMED); + } + self.confirmed_status(self.resolve_tx_index(txid)?) + } + pub fn transaction_raw(&self, txid: &Txid) -> Result> { if let Some(mempool) = self.mempool() && let Some(tx_with_hex) = mempool.get_txs().get(txid) @@ -77,48 +95,22 @@ impl Query { return Vec::from_hex(tx_with_hex.hex()) .map_err(|_| Error::Parse("Failed to decode mempool tx hex".into())); } - - let prefix = TxidPrefix::from(txid); - let indexer = self.indexer(); - let Ok(Some(tx_index)) = indexer - .stores - .txid_prefix_to_tx_index - .get(&prefix) - .map(|opt| opt.map(|cow| cow.into_owned())) - else { - return Err(Error::UnknownTxid); - }; - self.transaction_raw_by_index(tx_index) + self.transaction_raw_by_index(self.resolve_tx_index(txid)?) } pub fn transaction_hex(&self, txid: &Txid) -> Result { - // First check mempool for unconfirmed transactions if let Some(mempool) = self.mempool() && let Some(tx_with_hex) = mempool.get_txs().get(txid) { return Ok(tx_with_hex.hex().to_string()); } - - // Look up confirmed transaction by txid prefix - let prefix = TxidPrefix::from(txid); - let indexer = self.indexer(); - let Ok(Some(tx_index)) = indexer - .stores - .txid_prefix_to_tx_index - .get(&prefix) - .map(|opt| opt.map(|cow| cow.into_owned())) - else { - return Err(Error::UnknownTxid); - }; - - self.transaction_hex_by_index(tx_index) + self.transaction_hex_by_index(self.resolve_tx_index(txid)?) } + // ── Outspend queries ─────────────────────────────────────────── + pub fn outspend(&self, txid: &Txid, vout: Vout) -> Result { - if self - .mempool() - .is_some_and(|m| m.get_txs().contains_key(txid)) - { + if self.mempool().is_some_and(|m| m.get_txs().contains_key(txid)) { return Ok(TxOutspend::UNSPENT); } let (_, first_txout, output_count) = self.resolve_tx_outputs(txid)?; @@ -135,7 +127,33 @@ impl Query { return Ok(vec![TxOutspend::UNSPENT; tx_with_hex.tx().output.len()]); } let (_, first_txout, output_count) = self.resolve_tx_outputs(txid)?; + self.resolve_outspends(first_txout, output_count) + } + /// Resolve spend status for a single output. Minimal reads. + fn resolve_outspend(&self, txout_index: TxOutIndex) -> Result { + let txin_index = self + .computer() + .outputs + .spent + .txin_index + .reader() + .get(usize::from(txout_index)); + + if txin_index == TxInIndex::UNSPENT { + return Ok(TxOutspend::UNSPENT); + } + + self.build_outspend(txin_index) + } + + /// Resolve spend status for a contiguous range of outputs. + /// Readers/cursors created once, reused for all outputs. + fn resolve_outspends( + &self, + first_txout: TxOutIndex, + output_count: usize, + ) -> Result> { let indexer = self.indexer(); let txin_index_reader = self.computer().outputs.spent.txin_index.reader(); let txid_reader = indexer.vecs.transactions.txid.reader(); @@ -144,6 +162,7 @@ impl Query { let mut input_tx_cursor = indexer.vecs.inputs.tx_index.cursor(); let mut first_txin_cursor = indexer.vecs.transactions.first_txin_index.cursor(); + let mut cached_status: Option<(Height, BlockHash, Timestamp)> = None; let mut outspends = Vec::with_capacity(output_count); for i in 0..output_count { let txin_index = txin_index_reader.get(usize::from(first_txout + Vout::from(i))); @@ -157,20 +176,18 @@ impl Query { let spending_first_txin = first_txin_cursor.get(spending_tx_index.to_usize()).data()?; let vin = Vin::from(usize::from(txin_index) - usize::from(spending_first_txin)); let spending_txid = txid_reader.get(spending_tx_index.to_usize()); - let spending_height = tx_heights.get_shared(spending_tx_index).data()?; + let spending_height: Height = tx_heights.get_shared(spending_tx_index).data()?; - let block_hash = indexer - .vecs - .blocks - .cached_blockhash - .collect_one(spending_height) - .data()?; - let block_time = indexer - .vecs - .blocks - .cached_timestamp - .collect_one(spending_height) - .data()?; + let (block_hash, block_time) = + if let Some((h, ref bh, bt)) = cached_status + && h == spending_height + { + (bh.clone(), bt) + } else { + let (bh, bt) = self.block_hash_and_time(spending_height)?; + cached_status = Some((spending_height, bh.clone(), bt)); + (bh, bt) + }; outspends.push(TxOutspend { spent: true, @@ -188,16 +205,48 @@ impl Query { Ok(outspends) } + /// Build a single TxOutspend from a known-spent TxInIndex. + fn build_outspend(&self, txin_index: TxInIndex) -> Result { + let indexer = self.indexer(); + let spending_tx_index: TxIndex = indexer + .vecs + .inputs + .tx_index + .collect_one_at(usize::from(txin_index)) + .data()?; + let spending_first_txin: TxInIndex = indexer + .vecs + .transactions + .first_txin_index + .collect_one(spending_tx_index) + .data()?; + let vin = Vin::from(usize::from(txin_index) - usize::from(spending_first_txin)); + let spending_txid = indexer + .vecs + .transactions + .txid + .reader() + .get(spending_tx_index.to_usize()); + let spending_height = self.confirmed_status_height(spending_tx_index)?; + let (block_hash, block_time) = self.block_hash_and_time(spending_height)?; + + Ok(TxOutspend { + spent: true, + txid: Some(spending_txid), + vin: Some(vin), + status: Some(TxStatus { + confirmed: true, + block_height: Some(spending_height), + block_hash: Some(block_hash), + block_time: Some(block_time), + }), + }) + } + /// Resolve txid to (tx_index, first_txout_index, output_count). fn resolve_tx_outputs(&self, txid: &Txid) -> Result<(TxIndex, TxOutIndex, usize)> { - let prefix = TxidPrefix::from(txid); + let tx_index = self.resolve_tx_index(txid)?; let indexer = self.indexer(); - let tx_index: TxIndex = indexer - .stores - .txid_prefix_to_tx_index - .get(&prefix)? - .map(|cow| cow.into_owned()) - .ok_or(Error::UnknownTxid)?; let first = indexer .vecs .transactions @@ -211,76 +260,6 @@ impl Query { Ok((tx_index, first, usize::from(next) - usize::from(first))) } - /// Resolve spend status for a single output. - fn resolve_outspend(&self, txout_index: TxOutIndex) -> Result { - let indexer = self.indexer(); - let txin_index = self - .computer() - .outputs - .spent - .txin_index - .reader() - .get(usize::from(txout_index)); - - if txin_index == TxInIndex::UNSPENT { - return Ok(TxOutspend::UNSPENT); - } - - let spending_tx_index = indexer - .vecs - .inputs - .tx_index - .collect_one_at(usize::from(txin_index)) - .data()?; - let spending_first_txin = indexer - .vecs - .transactions - .first_txin_index - .collect_one(spending_tx_index) - .data()?; - let spending_height = self - .computer() - .indexes - .tx_heights - .get_shared(spending_tx_index) - .data()?; - - Ok(TxOutspend { - spent: true, - txid: Some( - indexer - .vecs - .transactions - .txid - .reader() - .get(spending_tx_index.to_usize()), - ), - vin: Some(Vin::from( - usize::from(txin_index) - usize::from(spending_first_txin), - )), - status: Some(TxStatus { - confirmed: true, - block_height: Some(spending_height), - block_hash: Some( - indexer - .vecs - .blocks - .blockhash - .reader() - .get(spending_height.to_usize()), - ), - block_time: Some( - indexer - .vecs - .blocks - .timestamp - .collect_one(spending_height) - .data()?, - ), - }), - }) - } - // === Helper methods === pub fn transaction_by_index(&self, tx_index: TxIndex) -> Result { @@ -313,24 +292,6 @@ impl Query { .to_lower_hex_string()) } - pub fn resolve_tx(&self, txid: &Txid) -> Result<(TxIndex, Height)> { - let indexer = self.indexer(); - let prefix = TxidPrefix::from(txid); - let tx_index: TxIndex = indexer - .stores - .txid_prefix_to_tx_index - .get(&prefix)? - .map(|cow| cow.into_owned()) - .ok_or(Error::UnknownTxid)?; - let height: Height = self - .computer() - .indexes - .tx_heights - .get_shared(tx_index) - .data()?; - Ok((tx_index, height)) - } - pub fn broadcast_transaction(&self, hex: &str) -> Result { self.client().send_raw_transaction(hex) } diff --git a/crates/brk_server/src/state.rs b/crates/brk_server/src/state.rs index c67b91e1d..047366a65 100644 --- a/crates/brk_server/src/state.rs +++ b/crates/brk_server/src/state.rs @@ -189,26 +189,8 @@ impl AppState { F: FnOnce(&brk_query::Query) -> brk_error::Result + Send + 'static, { self.cached(headers, strategy, uri, "application/json", move |q, enc| { - let t0 = std::time::Instant::now(); let value = f(q)?; - let t_query = t0.elapsed(); - let json = serde_json::to_vec(&value).unwrap(); - let t_json = t0.elapsed(); - let json_len = json.len(); - let compressed = enc.compress(Bytes::from(json)); - let t_total = t0.elapsed(); - if t_total.as_millis() > 100 { - eprintln!( - "[perf] query={:.1?} json={:.1?}({:.1}MB) compress={:.1?}({}) total={:.1?}", - t_query, - t_json - t_query, - json_len as f64 / 1_048_576.0, - t_total - t_json, - enc.as_str(), - t_total, - ); - } - Ok(compressed) + Ok(enc.compress(Bytes::from(serde_json::to_vec(&value).unwrap()))) }) .await }