diff --git a/resource/ckb.toml b/resource/ckb.toml index 6c60901b97..aa09a297a1 100644 --- a/resource/ckb.toml +++ b/resource/ckb.toml @@ -199,3 +199,11 @@ block_uncles_cache_size = 30 # [indexer_v2] # # Indexing the pending txs in the ckb tx-pool # index_tx_pool = false +# # Customize block filtering rules to index only retained blocks +# block_filter = "block.header.number.to_uint() >= \"0x0\".to_uint()" +# # Customize cell filtering rules to index only retained cells +# cell_filter = "let script = output.type;script!=() && script.code_hash == \"0x00000000000000000000000000000000000000000000000000545950455f4944\"" +# # The initial tip number and hash can be set as the starting height for building indexes in indexer-r. Effective only during the initial index creation. +# init_tip_number = 10000 +# # The initial tip hash must match the tip number; otherwise, it will result in a rollback to empty. +# init_tip_hash = "0x8fbd0ec887159d2814cee475911600e3589849670f5ee1ed9798b38fdeef4e44" diff --git a/util/app-config/src/configs/indexer.rs b/util/app-config/src/configs/indexer.rs index e1621dfad0..c83fae2385 100644 --- a/util/app-config/src/configs/indexer.rs +++ b/util/app-config/src/configs/indexer.rs @@ -1,3 +1,4 @@ +use ckb_types::H256; use serde::{Deserialize, Serialize}; use std::num::NonZeroUsize; use std::path::{Path, PathBuf}; @@ -28,6 +29,12 @@ pub struct IndexerConfig { /// Maximal db info log files to be kept. #[serde(default)] pub db_keep_log_file_num: Option, + /// The init tip block number + #[serde(default)] + pub init_tip_number: Option, + /// The init tip block hash + #[serde(default)] + pub init_tip_hash: Option, } const fn default_poll_interval() -> u64 { @@ -45,6 +52,8 @@ impl Default for IndexerConfig { cell_filter: None, db_background_jobs: None, db_keep_log_file_num: None, + init_tip_number: None, + init_tip_hash: None, } } } diff --git a/util/indexer/src/indexer.rs b/util/indexer/src/indexer.rs index 45939f708d..3ecc706527 100644 --- a/util/indexer/src/indexer.rs +++ b/util/indexer/src/indexer.rs @@ -997,7 +997,7 @@ impl CustomFilters { #[cfg(test)] mod tests { use super::*; - use crate::store::RocksdbStore; + use crate::{store::RocksdbStore, IndexerService}; use ckb_types::{ bytes::Bytes, core::{ @@ -2015,6 +2015,20 @@ mod tests { ) } + fn new_indexer_with_init_tip( + prefix: &str, + init_tip_number: Option, + init_tip_hash: Option, + ) -> Indexer { + let tmp_dir = tempfile::Builder::new().prefix(prefix).tempdir().unwrap(); + let store = RocksdbStore::new( + &RocksdbStore::default_options(), + tmp_dir.path().to_str().unwrap(), + ); + IndexerService::apply_init_tip(false, init_tip_number, &init_tip_hash, &store); + Indexer::new(store, 10, 1, None, CustomFilters::new(None, None)) + } + #[test] fn with_custom_block_filter() { let indexer = new_indexer_with_custom_filters::( @@ -2364,4 +2378,20 @@ mod tests { .len() ); } + + #[test] + fn rollback_with_set_init_tip() { + let indexer = new_indexer_with_init_tip( + "rollback_with_set_init_tip", + Some(1000), + Some(H256::default()), + ); + + indexer.rollback().unwrap(); + + // tip should be None and store should be empty; + assert!(indexer.tip().unwrap().is_none()); + let mut iter = indexer.store.iter([], IteratorDirection::Forward).unwrap(); + assert!(iter.next().is_none()); + } } diff --git a/util/indexer/src/service.rs b/util/indexer/src/service.rs index 1526074dd5..6c7374beb6 100644 --- a/util/indexer/src/service.rs +++ b/util/indexer/src/service.rs @@ -2,7 +2,7 @@ use crate::indexer::{self, extract_raw_data, CustomFilters, Indexer, Key, KeyPrefix, Value}; use crate::pool::Pool; -use crate::store::{IteratorDirection, RocksdbStore, SecondaryDB, Store}; +use crate::store::{Batch, IteratorDirection, RocksdbStore, SecondaryDB, Store}; use crate::error::Error; use ckb_app_config::{DBConfig, IndexerConfig}; @@ -46,8 +46,15 @@ pub struct IndexerService { impl IndexerService { /// Construct new Indexer service instance from DBConfig and IndexerConfig pub fn new(ckb_db_config: &DBConfig, config: &IndexerConfig, async_handle: Handle) -> Self { + let is_store_initialized = config.store.exists(); let store_opts = Self::indexer_store_options(config); let store = RocksdbStore::new(&store_opts, &config.store); + Self::apply_init_tip( + is_store_initialized, + config.init_tip_number, + &config.init_tip_hash, + &store, + ); let pool = if config.index_tx_pool { Some(Arc::new(RwLock::new(Pool::default()))) } else { @@ -247,6 +254,26 @@ impl IndexerService { ); opts } + + pub(crate) fn apply_init_tip( + is_store_initialized: bool, + init_tip_number: Option, + init_tip_hash: &Option, + store: &RocksdbStore, + ) { + if let (true, Some(init_tip_number), Some(init_tip_hash)) = + (!is_store_initialized, init_tip_number, init_tip_hash) + { + let mut batch = store.batch().expect("create batch should be OK"); + batch + .put_kv( + Key::Header(init_tip_number, &init_tip_hash.pack(), true), + vec![], + ) + .expect("insert init tip header should be OK"); + batch.commit().expect("commit batch should be OK"); + } + } } /// Handle to the indexer. @@ -1554,6 +1581,22 @@ mod tests { ); } + #[test] + fn rpc_get_indexer_tip_with_set_init_tip() { + let store = new_store("rpc_get_indexer_tip_with_set_init_tip"); + IndexerService::apply_init_tip(false, Some(10000), &Some(H256::default()), &store); + let pool = Arc::new(RwLock::new(Pool::default())); + let rpc = IndexerHandle { + store, + pool: Some(Arc::clone(&pool)), + }; + + // test get_tip rpc + let tip = rpc.get_indexer_tip().unwrap().unwrap(); + assert_eq!(H256::default(), tip.block_hash); + assert_eq!(10000, tip.block_number.value()); + } + #[test] fn script_search_mode_rpc() { let store = new_store("script_search_mode_rpc");