cuprated/blockchain/
interface.rs1use std::{
6 collections::{HashMap, HashSet},
7 sync::{Arc, Mutex},
8};
9
10use monero_oxide::{block::Block, transaction::Transaction};
11use tokio::sync::{mpsc, oneshot};
12use tower::{Service, ServiceExt};
13
14use cuprate_blockchain::service::BlockchainReadHandle;
15use cuprate_consensus::transactions::new_tx_verification_data;
16use cuprate_txpool::service::{
17 interface::{TxpoolReadRequest, TxpoolReadResponse},
18 TxpoolReadHandle,
19};
20use cuprate_types::blockchain::{BlockchainReadRequest, BlockchainResponse};
21
22use crate::{
23 blockchain::manager::{BlockchainManagerCommand, IncomingBlockOk},
24 constants::PANIC_CRITICAL_SERVICE_ERROR,
25};
26
27#[derive(Clone)]
31pub struct BlockchainManagerHandle {
32 command_tx: mpsc::Sender<BlockchainManagerCommand>,
34 blocks_being_handled: Arc<Mutex<HashSet<[u8; 32]>>>,
43}
44
45#[derive(Debug, thiserror::Error)]
47pub enum IncomingBlockError {
48 #[error("Unknown transactions in block.")]
52 UnknownTransactions([u8; 32], Vec<usize>),
53 #[error("The block has an unknown parent.")]
55 Orphan,
56 #[error(transparent)]
58 InvalidBlock(anyhow::Error),
59 #[error("The blockchain manager command channel is closed.")]
61 ChannelClosed,
62}
63
64impl BlockchainManagerHandle {
65 pub(crate) fn new() -> (Self, mpsc::Receiver<BlockchainManagerCommand>) {
67 let (command_tx, command_rx) = mpsc::channel(3);
68 (
69 Self {
70 command_tx,
71 blocks_being_handled: Arc::new(Mutex::new(HashSet::new())),
72 },
73 command_rx,
74 )
75 }
76
77 pub fn is_block_being_handled(&self, hash: &[u8; 32]) -> bool {
79 self.blocks_being_handled.lock().unwrap().contains(hash)
80 }
81
82 pub async fn handle_incoming_block(
94 &self,
95 block: Block,
96 mut given_txs: HashMap<[u8; 32], Transaction>,
97 blockchain_read_handle: &mut BlockchainReadHandle,
98 txpool_read_handle: &mut TxpoolReadHandle,
99 ) -> Result<IncomingBlockOk, IncomingBlockError> {
100 if given_txs.len() > block.transactions.len() {
101 return Err(IncomingBlockError::InvalidBlock(anyhow::anyhow!(
102 "Too many transactions given for block"
103 )));
104 }
105
106 if !block_exists(block.header.previous, blockchain_read_handle)
107 .await
108 .expect(PANIC_CRITICAL_SERVICE_ERROR)
109 {
110 return Err(IncomingBlockError::Orphan);
111 }
112
113 let block_hash = block.hash();
114
115 if block_exists(block_hash, blockchain_read_handle)
116 .await
117 .expect(PANIC_CRITICAL_SERVICE_ERROR)
118 {
119 return Ok(IncomingBlockOk::AlreadyHave);
120 }
121
122 let TxpoolReadResponse::TxsForBlock { mut txs, missing } = txpool_read_handle
123 .ready()
124 .await
125 .expect(PANIC_CRITICAL_SERVICE_ERROR)
126 .call(TxpoolReadRequest::TxsForBlock(block.transactions.clone()))
127 .await
128 .expect(PANIC_CRITICAL_SERVICE_ERROR)
129 else {
130 unreachable!()
131 };
132
133 if !missing.is_empty() {
134 let needed_hashes = missing.iter().map(|index| block.transactions[*index]);
135
136 for needed_hash in needed_hashes {
137 let Some(tx) = given_txs.remove(&needed_hash) else {
138 return Err(IncomingBlockError::UnknownTransactions(block_hash, missing));
143 };
144
145 txs.insert(
146 needed_hash,
147 new_tx_verification_data(tx)
148 .map_err(|e| IncomingBlockError::InvalidBlock(e.into()))?,
149 );
150 }
151 }
152
153 if !self.blocks_being_handled.lock().unwrap().insert(block_hash) {
155 return Ok(IncomingBlockOk::AlreadyHave);
157 }
158
159 let blocks = Arc::clone(&self.blocks_being_handled);
161 let _guard = {
162 struct RemoveFromBlocksBeingHandled {
163 block_hash: [u8; 32],
164 blocks: Arc<Mutex<HashSet<[u8; 32]>>>,
165 }
166 impl Drop for RemoveFromBlocksBeingHandled {
167 fn drop(&mut self) {
168 self.blocks.lock().unwrap().remove(&self.block_hash);
169 }
170 }
171 RemoveFromBlocksBeingHandled { block_hash, blocks }
172 };
173
174 let (response_tx, response_rx) = oneshot::channel();
175
176 self.command_tx
177 .send(BlockchainManagerCommand::AddBlock {
178 block,
179 prepped_txs: txs,
180 response_tx,
181 })
182 .await
183 .map_err(|_| IncomingBlockError::ChannelClosed)?;
184
185 response_rx
186 .await
187 .map_err(|_| IncomingBlockError::ChannelClosed)?
188 .map_err(IncomingBlockError::InvalidBlock)
189 }
190
191 pub async fn pop_blocks(&self, numb_blocks: usize) -> Result<(), anyhow::Error> {
197 let (response_tx, response_rx) = oneshot::channel();
198
199 self.command_tx
200 .send(BlockchainManagerCommand::PopBlocks {
201 numb_blocks,
202 response_tx,
203 })
204 .await?;
205
206 Ok(response_rx.await?)
207 }
208}
209
210async fn block_exists(
212 block_hash: [u8; 32],
213 blockchain_read_handle: &mut BlockchainReadHandle,
214) -> Result<bool, anyhow::Error> {
215 let BlockchainResponse::FindBlock(chain) = blockchain_read_handle
216 .ready()
217 .await?
218 .call(BlockchainReadRequest::FindBlock(block_hash))
219 .await?
220 else {
221 unreachable!();
222 };
223
224 Ok(chain.is_some())
225}