Skip to main content

ckb_store/
transaction.rs

1use crate::cache::StoreCache;
2use crate::store::ChainStore;
3use ckb_chain_spec::versionbits::VersionbitsIndexer;
4use ckb_db::{
5    DBPinnableSlice, RocksDBTransaction, RocksDBTransactionSnapshot,
6    iter::{DBIter, DBIterator, IteratorMode},
7};
8use ckb_db_schema::{
9    COLUMN_BLOCK_BODY, COLUMN_BLOCK_EPOCH, COLUMN_BLOCK_EXT, COLUMN_BLOCK_EXTENSION,
10    COLUMN_BLOCK_FILTER, COLUMN_BLOCK_FILTER_HASH, COLUMN_BLOCK_HEADER, COLUMN_BLOCK_PROPOSAL_IDS,
11    COLUMN_BLOCK_UNCLE, COLUMN_CELL, COLUMN_CELL_DATA, COLUMN_CELL_DATA_HASH,
12    COLUMN_CHAIN_ROOT_MMR, COLUMN_EPOCH, COLUMN_INDEX, COLUMN_META, COLUMN_NUMBER_HASH,
13    COLUMN_TRANSACTION_INFO, COLUMN_UNCLES, Col, META_CURRENT_EPOCH_KEY,
14    META_LATEST_BUILT_FILTER_DATA_KEY, META_TIP_HEADER_KEY,
15};
16use ckb_error::Error;
17use ckb_freezer::Freezer;
18use ckb_merkle_mountain_range::{Error as MMRError, MMRStore, Result as MMRResult};
19use ckb_types::{
20    core::{
21        BlockExt, BlockView, EpochExt, HeaderView, TransactionView,
22        cell::{CellChecker, CellProvider, CellStatus},
23    },
24    packed::{self, Byte32, OutPoint},
25    prelude::*,
26    utilities::calc_filter_hash,
27};
28use std::sync::Arc;
29
30/// A Transaction DB
31pub struct StoreTransaction {
32    pub(crate) inner: RocksDBTransaction,
33    pub(crate) freezer: Option<Freezer>,
34    pub(crate) cache: Arc<StoreCache>,
35}
36
37impl ChainStore for StoreTransaction {
38    fn cache(&self) -> Option<&StoreCache> {
39        Some(&self.cache)
40    }
41
42    fn freezer(&self) -> Option<&Freezer> {
43        self.freezer.as_ref()
44    }
45
46    fn get(&self, col: Col, key: &[u8]) -> Option<DBPinnableSlice<'_>> {
47        self.inner
48            .get_pinned(col, key)
49            .expect("db operation should be ok")
50    }
51
52    fn get_iter(&self, col: Col, mode: IteratorMode) -> DBIter<'_> {
53        self.inner
54            .iter(col, mode)
55            .expect("db operation should be ok")
56    }
57}
58
59impl VersionbitsIndexer for StoreTransaction {
60    fn block_epoch_index(&self, block_hash: &Byte32) -> Option<Byte32> {
61        ChainStore::get_block_epoch_index(self, block_hash)
62    }
63
64    fn epoch_ext(&self, index: &Byte32) -> Option<EpochExt> {
65        ChainStore::get_epoch_ext(self, index)
66    }
67
68    fn block_header(&self, block_hash: &Byte32) -> Option<HeaderView> {
69        ChainStore::get_block_header(self, block_hash)
70    }
71
72    fn cellbase(&self, block_hash: &Byte32) -> Option<TransactionView> {
73        ChainStore::get_cellbase(self, block_hash)
74    }
75}
76
77impl CellProvider for StoreTransaction {
78    fn cell(&self, out_point: &OutPoint, eager_load: bool) -> CellStatus {
79        match self.get_cell(out_point) {
80            Some(mut cell_meta) => {
81                if eager_load && let Some((data, data_hash)) = self.get_cell_data(out_point) {
82                    cell_meta.mem_cell_data = Some(data);
83                    cell_meta.mem_cell_data_hash = Some(data_hash);
84                }
85                CellStatus::live_cell(cell_meta)
86            }
87            None => CellStatus::Unknown,
88        }
89    }
90}
91
92impl CellChecker for StoreTransaction {
93    fn is_live(&self, out_point: &OutPoint) -> Option<bool> {
94        if self.have_cell(out_point) {
95            Some(true)
96        } else {
97            None
98        }
99    }
100}
101
102pub struct StoreTransactionSnapshot<'a> {
103    pub(crate) inner: RocksDBTransactionSnapshot<'a>,
104    pub(crate) freezer: Option<Freezer>,
105    pub(crate) cache: Arc<StoreCache>,
106}
107
108impl<'a> ChainStore for StoreTransactionSnapshot<'a> {
109    fn cache(&self) -> Option<&StoreCache> {
110        Some(&self.cache)
111    }
112
113    fn freezer(&self) -> Option<&Freezer> {
114        self.freezer.as_ref()
115    }
116
117    fn get(&self, col: Col, key: &[u8]) -> Option<DBPinnableSlice<'_>> {
118        self.inner
119            .get_pinned(col, key)
120            .expect("db operation should be ok")
121    }
122
123    fn get_iter(&self, col: Col, mode: IteratorMode) -> DBIter<'_> {
124        self.inner
125            .iter(col, mode)
126            .expect("db operation should be ok")
127    }
128}
129
130impl StoreTransaction {
131    /// Inserts a raw key-value pair into the specified column.
132    pub fn insert_raw(&self, col: Col, key: &[u8], value: &[u8]) -> Result<(), Error> {
133        self.inner.put(col, key, value)
134    }
135
136    /// Deletes a key from the specified column.
137    pub fn delete(&self, col: Col, key: &[u8]) -> Result<(), Error> {
138        self.inner.delete(col, key)
139    }
140
141    /// Commits the transaction, writing all changes to the database.
142    pub fn commit(&self) -> Result<(), Error> {
143        self.inner.commit()
144    }
145
146    /// Returns a snapshot of the transaction's current state.
147    pub fn get_snapshot(&self) -> StoreTransactionSnapshot<'_> {
148        StoreTransactionSnapshot {
149            inner: self.inner.get_snapshot(),
150            freezer: self.freezer.clone(),
151            cache: Arc::clone(&self.cache),
152        }
153    }
154
155    /// Gets the tip header hash for update, locking the row in the transaction.
156    pub fn get_update_for_tip_hash(
157        &self,
158        snapshot: &StoreTransactionSnapshot<'_>,
159    ) -> Option<packed::Byte32> {
160        self.inner
161            .get_for_update(COLUMN_META, META_TIP_HEADER_KEY, &snapshot.inner)
162            .expect("db operation should be ok")
163            .map(|slice| packed::Byte32Reader::from_slice_should_be_ok(slice.as_ref()).to_entity())
164    }
165
166    /// Inserts or updates the tip header hash in the store.
167    pub fn insert_tip_header(&self, h: &HeaderView) -> Result<(), Error> {
168        self.insert_raw(COLUMN_META, META_TIP_HEADER_KEY, h.hash().as_slice())
169    }
170
171    /// Inserts a block into the store.
172    pub fn insert_block(&self, block: &BlockView) -> Result<(), Error> {
173        let hash = block.hash();
174        let header = Into::<packed::HeaderView>::into(block.header());
175        let uncles = Into::<packed::UncleBlockVecView>::into(block.uncles());
176        let proposals = block.data().proposals();
177        let txs_len: packed::Uint32 = (block.transactions().len() as u32).into();
178        self.insert_raw(COLUMN_BLOCK_HEADER, hash.as_slice(), header.as_slice())?;
179        self.insert_raw(COLUMN_BLOCK_UNCLE, hash.as_slice(), uncles.as_slice())?;
180        if let Some(extension) = block.extension() {
181            self.insert_raw(
182                COLUMN_BLOCK_EXTENSION,
183                hash.as_slice(),
184                extension.as_slice(),
185            )?;
186        }
187        self.insert_raw(
188            COLUMN_NUMBER_HASH,
189            packed::NumberHash::new_builder()
190                .number(block.number())
191                .block_hash(hash.clone())
192                .build()
193                .as_slice(),
194            txs_len.as_slice(),
195        )?;
196        self.insert_raw(
197            COLUMN_BLOCK_PROPOSAL_IDS,
198            hash.as_slice(),
199            proposals.as_slice(),
200        )?;
201        for (index, tx) in block.transactions().into_iter().enumerate() {
202            let key = packed::TransactionKey::new_builder()
203                .block_hash(hash.clone())
204                .index(index)
205                .build();
206            let tx_data = Into::<packed::TransactionView>::into(tx);
207            self.insert_raw(COLUMN_BLOCK_BODY, key.as_slice(), tx_data.as_slice())?;
208        }
209        Ok(())
210    }
211
212    /// Deletes a block from the store.
213    pub fn delete_block(&self, block: &BlockView) -> Result<(), Error> {
214        let hash = block.hash();
215        let txs_len = block.transactions().len();
216        self.delete(COLUMN_BLOCK_HEADER, hash.as_slice())?;
217        self.delete(COLUMN_BLOCK_UNCLE, hash.as_slice())?;
218        self.delete(COLUMN_BLOCK_EXTENSION, hash.as_slice())?;
219        self.delete(COLUMN_BLOCK_PROPOSAL_IDS, hash.as_slice())?;
220        self.delete(
221            COLUMN_NUMBER_HASH,
222            packed::NumberHash::new_builder()
223                .number(block.number())
224                .block_hash(hash.clone())
225                .build()
226                .as_slice(),
227        )?;
228        // currently rocksdb transaction do not support `DeleteRange`
229        // https://github.com/facebook/rocksdb/issues/4812
230        for index in 0..txs_len {
231            let key = packed::TransactionKey::new_builder()
232                .block_hash(hash.clone())
233                .index(index)
234                .build();
235            self.delete(COLUMN_BLOCK_BODY, key.as_slice())?;
236        }
237        Ok(())
238    }
239
240    /// Inserts block extension data.
241    pub fn insert_block_ext(
242        &self,
243        block_hash: &packed::Byte32,
244        ext: &BlockExt,
245    ) -> Result<(), Error> {
246        let packed_ext: packed::BlockExtV1 = ext.into();
247        self.insert_raw(
248            COLUMN_BLOCK_EXT,
249            block_hash.as_slice(),
250            packed_ext.as_slice(),
251        )
252    }
253
254    /// Attaches a block to the main chain, indexing its transactions and uncles.
255    pub fn attach_block(&self, block: &BlockView) -> Result<(), Error> {
256        let header = block.data().header();
257        let block_hash = block.hash();
258        for (index, tx_hash) in block.tx_hashes().iter().enumerate() {
259            let key = packed::TransactionKey::new_builder()
260                .block_hash(block_hash.clone())
261                .index(index)
262                .build();
263            let info = packed::TransactionInfo::new_builder()
264                .key(key)
265                .block_number(header.raw().number())
266                .block_epoch(header.raw().epoch())
267                .build();
268            self.insert_raw(COLUMN_TRANSACTION_INFO, tx_hash.as_slice(), info.as_slice())?;
269        }
270        let block_number: packed::Uint64 = block.number().into();
271        self.insert_raw(COLUMN_INDEX, block_number.as_slice(), block_hash.as_slice())?;
272        for uncle in block.uncles().into_iter() {
273            self.insert_raw(
274                COLUMN_UNCLES,
275                uncle.hash().as_slice(),
276                Into::<packed::HeaderView>::into(uncle.header()).as_slice(),
277            )?;
278        }
279        self.insert_raw(COLUMN_INDEX, block_hash.as_slice(), block_number.as_slice())
280    }
281
282    /// Detaches a block from the main chain, removing its transaction and uncle indices.
283    pub fn detach_block(&self, block: &BlockView) -> Result<(), Error> {
284        for tx_hash in block.tx_hashes().iter() {
285            self.delete(COLUMN_TRANSACTION_INFO, tx_hash.as_slice())?;
286        }
287        for uncle in block.uncles().into_iter() {
288            self.delete(COLUMN_UNCLES, uncle.hash().as_slice())?;
289        }
290        let block_number = block.data().header().raw().number();
291        self.delete(COLUMN_INDEX, block_number.as_slice())?;
292        self.delete(COLUMN_INDEX, block.hash().as_slice())
293    }
294
295    /// Inserts the block-to-epoch index mapping.
296    pub fn insert_block_epoch_index(
297        &self,
298        block_hash: &packed::Byte32,
299        epoch_hash: &packed::Byte32,
300    ) -> Result<(), Error> {
301        self.insert_raw(
302            COLUMN_BLOCK_EPOCH,
303            block_hash.as_slice(),
304            epoch_hash.as_slice(),
305        )
306    }
307
308    /// Inserts epoch extension data.
309    pub fn insert_epoch_ext(&self, hash: &packed::Byte32, epoch: &EpochExt) -> Result<(), Error> {
310        self.insert_raw(
311            COLUMN_EPOCH,
312            hash.as_slice(),
313            Into::<packed::EpochExt>::into(epoch).as_slice(),
314        )?;
315        let epoch_number: packed::Uint64 = epoch.number().into();
316        self.insert_raw(COLUMN_EPOCH, epoch_number.as_slice(), hash.as_slice())
317    }
318
319    /// Inserts the current epoch extension data.
320    pub fn insert_current_epoch_ext(&self, epoch: &EpochExt) -> Result<(), Error> {
321        self.insert_raw(
322            COLUMN_META,
323            META_CURRENT_EPOCH_KEY,
324            Into::<packed::EpochExt>::into(epoch).as_slice(),
325        )
326    }
327
328    /// Inserts multiple cells into the store.
329    pub fn insert_cells(
330        &self,
331        cells: impl Iterator<
332            Item = (
333                packed::OutPoint,
334                packed::CellEntry,
335                Option<packed::CellDataEntry>,
336            ),
337        >,
338    ) -> Result<(), Error> {
339        for (out_point, cell, cell_data) in cells {
340            let key = out_point.to_cell_key();
341            self.insert_raw(COLUMN_CELL, &key, cell.as_slice())?;
342            if let Some(data) = cell_data {
343                self.insert_raw(COLUMN_CELL_DATA, &key, data.as_slice())?;
344                self.insert_raw(
345                    COLUMN_CELL_DATA_HASH,
346                    &key,
347                    data.output_data_hash().as_slice(),
348                )?;
349            } else {
350                self.insert_raw(COLUMN_CELL_DATA, &key, &[])?;
351                self.insert_raw(COLUMN_CELL_DATA_HASH, &key, &[])?;
352            }
353        }
354        Ok(())
355    }
356
357    /// Deletes multiple cells from the store.
358    pub fn delete_cells(
359        &self,
360        out_points: impl Iterator<Item = packed::OutPoint>,
361    ) -> Result<(), Error> {
362        for out_point in out_points {
363            let key = out_point.to_cell_key();
364            self.delete(COLUMN_CELL, &key)?;
365            self.delete(COLUMN_CELL_DATA, &key)?;
366            self.delete(COLUMN_CELL_DATA_HASH, &key)?;
367        }
368        Ok(())
369    }
370
371    /// Inserts a header digest.
372    pub fn insert_header_digest(
373        &self,
374        position_u64: u64,
375        header_digest: &packed::HeaderDigest,
376    ) -> Result<(), Error> {
377        let position: packed::Uint64 = position_u64.into();
378        self.insert_raw(
379            COLUMN_CHAIN_ROOT_MMR,
380            position.as_slice(),
381            header_digest.as_slice(),
382        )
383    }
384
385    /// Deletes a header digest.
386    pub fn delete_header_digest(&self, position_u64: u64) -> Result<(), Error> {
387        let position: packed::Uint64 = position_u64.into();
388        self.delete(COLUMN_CHAIN_ROOT_MMR, position.as_slice())
389    }
390
391    /// insert block filter data
392    pub fn insert_block_filter(
393        &self,
394        block_hash: &packed::Byte32,
395        filter_data: &packed::Bytes,
396        parent_block_filter_hash: &packed::Byte32,
397    ) -> Result<(), Error> {
398        self.insert_raw(
399            COLUMN_BLOCK_FILTER,
400            block_hash.as_slice(),
401            filter_data.as_slice(),
402        )?;
403        let current_block_filter_hash = calc_filter_hash(parent_block_filter_hash, filter_data);
404        self.insert_raw(
405            COLUMN_BLOCK_FILTER_HASH,
406            block_hash.as_slice(),
407            current_block_filter_hash.as_slice(),
408        )?;
409        self.insert_raw(
410            COLUMN_META,
411            META_LATEST_BUILT_FILTER_DATA_KEY,
412            block_hash.as_slice(),
413        )
414    }
415}
416
417impl MMRStore<packed::HeaderDigest> for &StoreTransaction {
418    fn get_elem(&self, pos: u64) -> MMRResult<Option<packed::HeaderDigest>> {
419        Ok(self.get_header_digest(pos))
420    }
421
422    fn append(&mut self, pos: u64, elems: Vec<packed::HeaderDigest>) -> MMRResult<()> {
423        for (offset, elem) in elems.iter().enumerate() {
424            let pos: u64 = pos + (offset as u64);
425            self.insert_header_digest(pos, elem).map_err(|err| {
426                MMRError::StoreError(format!("Failed to append to MMR, DB error {err}"))
427            })?;
428        }
429        Ok(())
430    }
431}