cuprate_blockchain/service/
write.rs1use std::{
4 borrow::Cow,
5 sync::Arc,
6 task::{Context, Poll},
7};
8
9use crossbeam::channel::Receiver;
10use fjall::PersistMode;
11use futures::channel::oneshot;
12use tapes::TapesRead;
13use tower::Service;
14use tracing::instrument;
15
16use cuprate_helper::cast::u64_to_usize;
17use cuprate_pruning::CRYPTONOTE_PRUNING_TIP_BLOCKS;
18use cuprate_types::{
19 blockchain::{BlockchainResponse, BlockchainWriteRequest},
20 AltBlockInformation, ChainId, VerifiedBlockInformation,
21};
22
23use crate::{
24 config::Persistence,
25 error::{BlockchainError, DbResult},
26 ops::block::add_blocks_to_tapes,
27 service::ResponseResult,
28 BlockchainDatabase,
29};
30
31pub fn init_write_service(env: Arc<BlockchainDatabase>) -> BlockchainWriteHandle {
34 let (sender, receiver) = crossbeam::channel::unbounded();
35
36 std::thread::Builder::new()
37 .name("cuprate_blockchain_writer".into())
38 .spawn(move || writer_thread(&env, &receiver))
39 .unwrap();
40
41 BlockchainWriteHandle { sender }
42}
43
44pub struct BlockchainWriteHandle {
46 sender: crossbeam::channel::Sender<(
50 BlockchainWriteRequest,
51 oneshot::Sender<DbResult<BlockchainResponse>>,
52 )>,
53}
54
55impl Service<BlockchainWriteRequest> for BlockchainWriteHandle {
56 type Response = BlockchainResponse;
57 type Error = BlockchainError;
58 type Future = cuprate_helper::asynch::InfallibleOneshotReceiver<DbResult<BlockchainResponse>>;
59
60 fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
61 Poll::Ready(Ok(()))
62 }
63
64 fn call(&mut self, req: BlockchainWriteRequest) -> Self::Future {
65 let (response_sender, receiver) = oneshot::channel();
66
67 self.sender.try_send((req, response_sender)).unwrap();
68
69 cuprate_helper::asynch::InfallibleOneshotReceiver::from(receiver)
70 }
71}
72
73#[instrument(
74 name = "blockchain_writer_thread",
75 skip(env, receiver),
76 level = "error"
77)]
78fn writer_thread(
79 env: &Arc<BlockchainDatabase>,
80 receiver: &Receiver<(
81 BlockchainWriteRequest,
82 oneshot::Sender<DbResult<BlockchainResponse>>,
83 )>,
84) {
85 while let Ok((req, response_sender)) = receiver.recv() {
86 let span = tracing::debug_span!("write_request");
87 span.in_scope(|| {
88 let response = handle_blockchain_request(env, &req);
89
90 match &response {
91 Ok(_) => tracing::debug!("Sending successful write response."),
92 Err(e) => {
93 tracing::error!("Failed to handle write request: {e:?}");
94 }
95 }
96
97 let _ = response_sender.send(response).inspect_err(|_| {
98 tracing::warn!("Failed to send write response, rx wasn't waiting.");
99 });
100 });
101 }
102}
103
104fn handle_blockchain_request(
107 env: &Arc<BlockchainDatabase>,
108 req: &BlockchainWriteRequest,
109) -> Result<BlockchainResponse, BlockchainError> {
110 match req {
111 BlockchainWriteRequest::WriteBlock(block) => write_block(env, block),
112 BlockchainWriteRequest::BatchWriteBlocks(blocks) => write_blocks(env, blocks),
113 BlockchainWriteRequest::WriteAltBlock(alt_block) => write_alt_block(env, alt_block),
114 BlockchainWriteRequest::PopBlocks(numb_blocks, keep) => {
115 pop_blocks(env, *numb_blocks, *keep)
116 }
117 BlockchainWriteRequest::FlushAltBlocks => flush_alt_blocks(env),
118 }
119}
120
121#[inline]
132#[instrument(skip(db, block), level = "debug")]
133fn write_block(db: &BlockchainDatabase, block: &VerifiedBlockInformation) -> ResponseResult {
134 write_blocks(db, std::slice::from_ref(block))
135}
136
137#[inline]
139#[instrument(skip(db, blocks), level = "debug")]
140fn write_blocks(db: &BlockchainDatabase, blocks: &[VerifiedBlockInformation]) -> ResponseResult {
141 debug_assert!(blocks.len() <= CRYPTONOTE_PRUNING_TIP_BLOCKS);
142 let (tapes_persist_mode, fjall_persist_mode) = match db.config.persistence {
143 Persistence::Buffer => (tapes::Persistence::Buffer, PersistMode::Buffer),
144 Persistence::BufferThenSync if blocks.len() > 1 => {
147 (tapes::Persistence::Buffer, PersistMode::Buffer)
148 }
149 Persistence::Sync | Persistence::BufferThenSync => {
150 (tapes::Persistence::SyncAll, PersistMode::SyncAll)
151 }
152 };
153
154 tracing::debug!("Writing {} block(s) to database.", blocks.len());
155
156 let mut tapes = db.linear_tapes.append();
157
158 let mut numb_transactions = tapes
159 .fixed_sized_tape_len(&db.tx_infos)
160 .expect("required tape not open");
161
162 add_blocks_to_tapes(blocks, db, &mut tapes)?;
163
164 tapes.commit(tapes_persist_mode)?;
165
166 let mut pre_rct_numb_outputs_cache = db.pre_rct_numb_outputs_cache.lock().unwrap();
167 let mut tx_rw = db.fjall.batch().durability(Some(fjall_persist_mode));
168
169 for block in blocks {
170 crate::ops::block::add_block_to_dynamic_tables(
171 db,
172 &block.block,
173 &block.block_hash,
174 block.txs.iter().map(|tx| Cow::Borrowed(&tx.tx)),
175 &mut numb_transactions,
176 &mut tx_rw,
177 &mut pre_rct_numb_outputs_cache,
178 )?;
179 }
180
181 tx_rw.commit()?;
182
183 Ok(BlockchainResponse::Ok)
184}
185
186#[inline]
188fn write_alt_block(db: &BlockchainDatabase, block: &AltBlockInformation) -> ResponseResult {
189 let mut tx_rw = db.fjall.batch().durability(Some(PersistMode::SyncAll));
190
191 crate::ops::alt_block::add_alt_block(db, block, &mut tx_rw)?;
192
193 tx_rw.commit()?;
194
195 Ok(BlockchainResponse::Ok)
196}
197
198fn pop_blocks(db: &BlockchainDatabase, numb_blocks: usize, keep_blocks: bool) -> ResponseResult {
200 let mut tapes = db.linear_tapes.truncate();
201 let mut tx_rw = db.fjall.batch().durability(Some(PersistMode::SyncAll));
202
203 crate::ops::alt_block::flush_alt_blocks(db)?;
205
206 let mut old_main_chain_id = keep_blocks.then_some(ChainId(rand::random()));
208
209 assert!(tapes
210 .fixed_sized_tape_len(&db.block_infos)
211 .is_some_and(|height| u64_to_usize(height) > numb_blocks));
212
213 for _ in 0..numb_blocks {
215 let (.., added_to_alt_chain) =
216 crate::ops::block::pop_block(db, old_main_chain_id, &mut tx_rw, &mut tapes)?;
217
218 if old_main_chain_id.is_some() && !added_to_alt_chain {
219 old_main_chain_id = None;
220 }
221 }
222
223 tapes.commit(tapes::Persistence::SyncAll)?;
224 tx_rw.commit()?;
225
226 if keep_blocks && old_main_chain_id.is_none() {
227 crate::ops::alt_block::flush_alt_blocks(db)?;
228 }
229
230 Ok(BlockchainResponse::PopBlocks(old_main_chain_id))
231}
232
233#[inline]
235fn flush_alt_blocks(db: &BlockchainDatabase) -> ResponseResult {
236 crate::ops::alt_block::flush_alt_blocks(db)?;
237
238 Ok(BlockchainResponse::Ok)
239}