Repository: nervosnetwork/ckb (language: rust)

Issue:
High-Intensity JSON RPC BatchRequest Causes Server Crash and Unpredictable Downtime
## Bug Report
<!-- Thanks for your bug report! We appreciate your time to fill following sections to help us locate the root cause. -->
### Current Behavior
When subjected to high-intensity JSON RPC BatchRequest, the CKB node causes the entire server to crash, rendering it completely inaccessible. The crash occurs after a certain batch size threshold is exceeded, and even after stopping the requests, the server will still crash at an unpredictable time in the future.

### Expected Behavior
I expect the node to send an error message to the client when it cannot handle high-intensity batch sizes, rather than attempting to process the requests and causing the server to crash. Ideally, the node should shut down to prevent affecting the entire server.

### Environment
- **CKB version**: ckb 0.116.1 
- **Chain**: testnet, mainnet (both have experienced the issue)
- **Operating system**: Ubuntu 20.04
- **Arch**: x64
- **Installation**: GitHub Release

### Additional context/Screenshots
The issue was first observed when my indexer node encountered the bug and stopped working. I had used high-frequency batch sizes for a few seconds before stopping the requests. Approximately two hours later, the server suddenly became unresponsive.



Discussion / hints:
Hello, What are the total memory, available memory, and CPU configuration of this server?

> high-intensity JSON RPC BatchRequest

Which RPC causes the entire server to crash?

> the CKB node causes the entire server to crash

Can you check the logs of the last crash? Can you find out why it crashed (most likely due to OOM)? You can use `sudo journalctl -b -1` to check.
I think this PR may help to resolve this issue:
https://github.com/nervosnetwork/ckb/pull/4459

it's on 117 version.



we have another working in progress PR try to limit the resource spent on heavy RPC:
https://github.com/nervosnetwork/ckb/pull/4469
@eval-exec 

The server's configuration is not critical. Previously, the server with lower specifications would crash when the batch size reached 1000. With the current server's improved specifications, it crashes when the batch size reaches 2000. The interface causing the crash is `get_block_by_number`.

Thank you for your efforts, @chenyukang ! We're eagerly looking forward to the launch of this feature to optimize resource usage. Currently, I've already determined the batch size that our server can tolerate, and to avoid any further crashes, I won't be conducting related tests for now. However, I believe this improvement will have a positive impact on our system, and I'm looking forward to the resolution of this issue.


rpc batch mode will execute all the requests concurrently and wait for them to complete and return together, notice that you are calling `get_block_by_number` rpc, because the returning result is a full block data in json format, the size will be very large, a single batch request can easily use up several gigabytes of memory. If there are multiple concurrent batch requests, this will result in OOM.

suggest to limit the batch size to a smaller number and use `verbosity=0` to return the block data in molecule format which is much smaller then json. ref: https://github.com/nervosnetwork/ckb/tree/develop/rpc#method-get_block_by_number
@Cupnfish Could you provide the output of `free -h`?
@eval-exec 
```
free -h
               total        used        free      shared  buff/cache   available
Mem:           125Gi       7.2Gi       2.0Gi        40Mi       117Gi       118Gi
Swap:             0B          0B          0B
```

Fix patch:
diff --git a/resource/ckb.toml b/resource/ckb.toml
index e47a37cff4..c2091ad87e 100644
--- a/resource/ckb.toml
+++ b/resource/ckb.toml
@@ -212,6 +212,12 @@ block_uncles_cache_size    = 30
 # cell_filter = "let script = output.type;script!=() && script.code_hash == \"0x00000000000000000000000000000000000000000000000000545950455f4944\""
 # # The initial tip can be set higher than the current indexer tip as the starting height for indexing.
 # init_tip_hash = "0x8fbd0ec887159d2814cee475911600e3589849670f5ee1ed9798b38fdeef4e44"
+# By default, there is no limitation on the size of indexer request
+# However, because serde json serialization consumes too much memory(10x),
+# it may cause the physical machine to become unresponsive.
+# We recommend a consumption limit of 2g, which is 400 as the limit,
+# which is a safer approach
+# request_limit = 400
 #
 # # CKB rich-indexer has its unique configuration.
 # [indexer_v2.rich_indexer]
diff --git a/util/app-config/src/configs/indexer.rs b/util/app-config/src/configs/indexer.rs
index 5966b6673b..1f44a68aa6 100644
--- a/util/app-config/src/configs/indexer.rs
+++ b/util/app-config/src/configs/indexer.rs
@@ -35,6 +35,9 @@ pub struct IndexerConfig {
     /// The init tip block hash
     #[serde(default)]
     pub init_tip_hash: Option<H256>,
+    /// limit of indexer reqeust
+    #[serde(default)]
+    pub request_limit: Option<usize>,
     /// Rich indexer config options
     #[serde(default)]
     pub rich_indexer: RichIndexerConfig,
@@ -56,6 +59,7 @@ impl Default for IndexerConfig {
             db_background_jobs: None,
             db_keep_log_file_num: None,
             init_tip_hash: None,
+            request_limit: None,
             rich_indexer: RichIndexerConfig::default(),
         }
     }
diff --git a/util/gen-types/src/lib.rs b/util/gen-types/src/lib.rs
index c2aa5c5183..bceae47fda 100644
--- a/util/gen-types/src/lib.rs
+++ b/util/gen-types/src/lib.rs
@@ -19,8 +19,10 @@ pub use molecule::bytes;
 
 cfg_if::cfg_if! {
     if #[cfg(feature = "std")] {
+        #[allow(unused_imports)]
         use std::{vec, borrow};
     } else {
+        #[allow(unused_imports)]
         use alloc::{vec, borrow};
     }
 }
diff --git a/util/indexer/src/service.rs b/util/indexer/src/service.rs
index 774276e392..849bb434c2 100644
--- a/util/indexer/src/service.rs
+++ b/util/indexer/src/service.rs
@@ -19,6 +19,7 @@ use rocksdb::{prelude::*, Direction, IteratorMode};
 use std::convert::TryInto;
 use std::num::NonZeroUsize;
 use std::sync::{Arc, RwLock};
+use std::usize;
 
 pub(crate) const SUBSCRIBER_NAME: &str = "Indexer";
 const DEFAULT_LOG_KEEP_NUM: usize = 1;
@@ -31,6 +32,7 @@ pub struct IndexerService {
     sync: IndexerSyncService,
     block_filter: Option<String>,
     cell_filter: Option<String>,
+    request_limit: usize,
 }
 
 impl IndexerService {
@@ -56,6 +58,7 @@ impl IndexerService {
             sync,
             block_filter: config.block_filter.clone(),
             cell_filter: config.cell_filter.clone(),
+            request_limit: config.request_limit.unwrap_or(usize::MAX),
         }
     }
 
@@ -67,6 +70,7 @@ impl IndexerService {
         IndexerHandle {
             store: self.store.clone(),
             pool: self.sync.pool(),
+            request_limit: self.request_limit,
         }
     }
 
@@ -124,6 +128,7 @@ impl IndexerService {
 pub struct IndexerHandle {
     pub(crate) store: RocksdbStore,
     pub(crate) pool: Option<Arc<RwLock<Pool>>>,
+    request_limit: usize,
 }
 
 impl IndexerHandle {
@@ -168,6 +173,12 @@ impl IndexerHandle {
         if limit == 0 {
             return Err(Error::invalid_params("limit should be greater than 0"));
         }
+        if limit > self.request_limit {
+            return Err(Error::invalid_params(format!(
+                "limit must be less than {}",
+                self.request_limit,
+            )));
+        }
 
         let (prefix, from_key, direction, skip) = build_query_options(
             &search_key,
@@ -334,6 +345,12 @@ impl IndexerHandle {
         if limit == 0 {
             return Err(Error::invalid_params("limit should be greater than 0"));
         }
+        if limit > self.request_limit {
+            return Err(Error::invalid_params(format!(
+                "limit must be less than {}",
+                self.request_limit,
+            )));
+        }
 
         if search_key
             .script_search_mode
@@ -926,12 +943,13 @@ mod tests {
 
     #[test]
     fn rpc() {
-        let store = new_store("rpc");
+        let store: RocksdbStore = new_store("rpc");
         let pool = Arc::new(RwLock::new(Pool::default()));
         let indexer = Indexer::new(store.clone(), 10, 100, None, CustomFilters::new(None, None));
         let rpc = IndexerHandle {
             store,
             pool: Some(Arc::clone(&pool)),
+            request_limit: usize::MAX,
         };
 
         // setup test data
@@ -1516,7 +1534,11 @@ mod tests {
     fn script_search_mode_rpc() {
         let store = new_store("script_search_mode_rpc");
         let indexer = Indexer::new(store.clone(), 10, 100, None, CustomFilters::new(None, None));
-        let rpc = IndexerHandle { store, pool: None };
+        let rpc = IndexerHandle {
+            store,
+            pool: None,
+            request_limit: usize::MAX,
+        };
 
         // setup test data
         let lock_script1 = ScriptBuilder::default()
@@ -1755,11 +1777,47 @@ mod tests {
         );
     }
 
+    #[test]
+    fn test_request_limit() {
+        let store = new_store("script_search_mode_rpc");
+        let rpc = IndexerHandle {
+            store,
+            pool: None,
+            request_limit: 2,
+        };
+
+        let lock_script1 = ScriptBuilder::default()
+            .code_hash(H256(rand::random()).pack())
+            .hash_type(ScriptHashType::Type.into())
+            .args(Bytes::from(b"lock_script1".to_vec()).pack())
+            .build();
+        let data = [0u8; 7];
+        let res = rpc.get_cells(
+            IndexerSearchKey {
+                script: lock_script1.into(),
+                filter: Some(IndexerSearchKeyFilter {
+                    output_data: Some(JsonBytes::from_vec(data.to_vec())),
+                    output_data_filter_mode: Some(IndexerSearchMode::Prefix),
+                    ..Default::default()
+                }),
+                ..Default::default()
+            },
+            IndexerOrder::Asc,
+            1000.into(),
+            None,
+        );
+        assert!(res.is_err())
+    }
+
     #[test]
     fn output_data_filter_mode_rpc() {
         let store = new_store("script_search_mode_rpc");
         let indexer = Indexer::new(store.clone(), 10, 100, None, CustomFilters::new(None, None));
-        let rpc = IndexerHandle { store, pool: None };
+        let rpc = IndexerHandle {
+            store,
+            pool: None,
+            request_limit: usize::MAX,
+        };
 
         // setup test data
         let lock_script1 = ScriptBuilder::default()
diff --git a/util/jsonrpc-types/src/blockchain.rs b/util/jsonrpc-types/src/blockchain.rs
index 47fbfff606..247b394c1c 100644
--- a/util/jsonrpc-types/src/blockchain.rs
+++ b/util/jsonrpc-types/src/blockchain.rs
@@ -116,7 +116,7 @@ impl From<packed::Script> for Script {
     fn from(input: packed::Script) -> Script {
         Script {
             code_hash: input.code_hash().unpack(),
-            args: JsonBytes::from_bytes(input.args().unpack()),
+            args: JsonBytes::from_vec(input.args().unpack()),
             hash_type: core::ScriptHashType::try_from(input.hash_type())
                 .expect("checked data")
                 .into(),
diff --git a/util/jsonrpc-types/src/bytes.rs b/util/jsonrpc-types/src/bytes.rs
index 1e270f8d54..b5b7a6bcf4 100644
--- a/util/jsonrpc-types/src/bytes.rs
+++ b/util/jsonrpc-types/src/bytes.rs
@@ -60,13 +60,13 @@ impl JsonBytes {
 
 impl From<packed::Bytes> for JsonBytes {
     fn from(input: packed::Bytes) -> Self {
-        JsonBytes::from_bytes(input.raw_data())
+        JsonBytes::from_vec(input.raw_data().to_vec())
     }
 }
 
 impl<'a> From<&'a packed::Bytes> for JsonBytes {
     fn from(input: &'a packed::Bytes) -> Self {
-        JsonBytes::from_bytes(input.raw_data())
+        JsonBytes::from_vec(input.raw_data().to_vec())
     }
 }
 
diff --git a/util/rich-indexer/src/indexer/mod.rs b/util/rich-indexer/src/indexer/mod.rs
index cd5beb3a67..32c395f775 100644
--- a/util/rich-indexer/src/indexer/mod.rs
+++ b/util/rich-indexer/src/indexer/mod.rs
@@ -37,6 +37,7 @@ use std::sync::{Arc, RwLock};
 pub(crate) struct RichIndexer {
     async_rich_indexer: AsyncRichIndexer,
     async_runtime: Handle,
+    request_limit: usize,
 }
 
 impl RichIndexer {
@@ -46,10 +47,12 @@ impl RichIndexer {
         pool: Option<Arc<RwLock<Pool>>>,
         custom_filters: CustomFilters,
         async_runtime: Handle,
+        request_limit: usize,
     ) -> Self {
         Self {
             async_rich_indexer: AsyncRichIndexer::new(store, pool, custom_filters),
             async_runtime,
+            request_limit,
         }
     }
 }
@@ -61,6 +64,7 @@ impl IndexerSync for RichIndexer {
             self.async_rich_indexer.store.clone(),
             self.async_rich_indexer.pool.clone(),
             self.async_runtime.clone(),
+            self.request_limit,
         );
         indexer_handle
             .get_indexer_tip()
diff --git a/util/rich-indexer/src/indexer_handle/async_indexer_handle/get_cells.rs b/util/rich-indexer/src/indexer_handle/async_indexer_handle/get_cells.rs
index 63a89d4800..100a5eaca0 100644
--- a/util/rich-indexer/src/indexer_handle/async_indexer_handle/get_cells.rs
+++ b/util/rich-indexer/src/indexer_handle/async_indexer_handle/get_cells.rs
@@ -26,6 +26,12 @@ impl AsyncRichIndexerHandle {
         if limit == 0 {
             return Err(Error::invalid_params("limit should be greater than 0"));
         }
+        if limit as usize > self.request_limit {
+            return Err(Error::invalid_params(format!(
+                "limit must be less than {}",
+                self.request_limit,
+            )));
+        }
 
         let mut param_index = 1;
 
diff --git a/util/rich-indexer/src/indexer_handle/async_indexer_handle/get_transactions.rs b/util/rich-indexer/src/indexer_handle/async_indexer_handle/get_transactions.rs
index d8d58e0d68..ff58aa0a6f 100644
--- a/util/rich-indexer/src/indexer_handle/async_indexer_handle/get_transactions.rs
+++ b/util/rich-indexer/src/indexer_handle/async_indexer_handle/get_transactions.rs
@@ -24,6 +24,12 @@ impl AsyncRichIndexerHandle {
         if limit == 0 {
             return Err(Error::invalid_params("limit should be greater than 0"));
         }
+        if limit as usize > self.request_limit {
+            return Err(Error::invalid_params(format!(
+                "limit must be less than {}",
+                self.request_limit,
+            )));
+        }
         search_key.filter = convert_max_values_in_search_filter(&search_key.filter);
 
         let mut tx = self
diff --git a/util/rich-indexer/src/indexer_handle/async_indexer_handle/mod.rs b/util/rich-indexer/src/indexer_handle/async_indexer_handle/mod.rs
index 7f6032ef25..4a99cbcc32 100644
--- a/util/rich-indexer/src/indexer_handle/async_indexer_handle/mod.rs
+++ b/util/rich-indexer/src/indexer_handle/async_indexer_handle/mod.rs
@@ -23,12 +23,17 @@ use std::sync::{Arc, RwLock};
 pub struct AsyncRichIndexerHandle {
     store: SQLXPool,
     pool: Option<Arc<RwLock<Pool>>>,
+    request_limit: usize,
 }
 
 impl AsyncRichIndexerHandle {
     /// Construct new AsyncRichIndexerHandle instance
-    pub fn new(store: SQLXPool, pool: Option<Arc<RwLock<Pool>>>) -> Self {
-        Self { store, pool }
+    pub fn new(store: SQLXPool, pool: Option<Arc<RwLock<Pool>>>, request_limit: usize) -> Self {
+        Self {
+            store,
+            pool,
+            request_limit,
+        }
     }
 }
 
diff --git a/util/rich-indexer/src/indexer_handle/mod.rs b/util/rich-indexer/src/indexer_handle/mod.rs
index 10d1e1e62e..f96c8f590c 100644
--- a/util/rich-indexer/src/indexer_handle/mod.rs
+++ b/util/rich-indexer/src/indexer_handle/mod.rs
@@ -22,9 +22,14 @@ pub struct RichIndexerHandle {
 
 impl RichIndexerHandle {
     /// Construct new RichIndexerHandle instance
-    pub fn new(store: SQLXPool, pool: Option<Arc<RwLock<Pool>>>, async_handle: Handle) -> Self {
+    pub fn new(
+        store: SQLXPool,
+        pool: Option<Arc<RwLock<Pool>>>,
+        async_handle: Handle,
+        request_limit: usize,
+    ) -> Self {
         Self {
-            async_handle: AsyncRichIndexerHandle::new(store, pool),
+            async_handle: AsyncRichIndexerHandle::new(store, pool, request_limit),
             async_runtime: async_handle,
         }
     }
diff --git a/util/rich-indexer/src/service.rs b/util/rich-indexer/src/service.rs
index 716b08f2d2..17c680c108 100644
--- a/util/rich-indexer/src/service.rs
+++ b/util/rich-indexer/src/service.rs
@@ -1,5 +1,7 @@
 //！The rich-indexer service.
 
+use std::usize;
+
 use crate::indexer::RichIndexer;
 use crate::store::SQLXPool;
 use crate::{AsyncRichIndexerHandle, RichIndexerHandle};
@@ -19,6 +21,7 @@ pub struct RichIndexerService {
     block_filter: Option<String>,
     cell_filter: Option<String>,
     async_handle: Handle,
+    request_limit: usize,
 }
 
 impl RichIndexerService {
@@ -47,6 +50,7 @@ impl RichIndexerService {
             block_filter: config.block_filter.clone(),
             cell_filter: config.cell_filter.clone(),
             async_handle,
+            request_limit: config.request_limit.unwrap_or(usize::MAX),
         }
     }
 
@@ -56,6 +60,7 @@ impl RichIndexerService {
             self.sync.pool(),
             CustomFilters::new(self.block_filter.as_deref(), self.cell_filter.as_deref()),
             self.async_handle.clone(),
+            self.request_limit,
         )
     }
 
@@ -83,6 +88,7 @@ impl RichIndexerService {
             self.store.clone(),
             self.sync.pool(),
             self.async_handle.clone(),
+            self.request_limit,
         )
     }
 
@@ -91,6 +97,6 @@ impl RichIndexerService {
     /// The returned handle can be used to get data from rich-indexer,
     /// and can be cloned to allow moving the Handle to other threads.
     pub fn async_handle(&self) -> AsyncRichIndexerHandle {
-        AsyncRichIndexerHandle::new(self.store.clone(), self.sync.pool())
+        AsyncRichIndexerHandle::new(self.store.clone(), self.sync.pool(), self.request_limit)
     }
 }


Test patch:
diff --git a/util/rich-indexer/src/tests/insert.rs b/util/rich-indexer/src/tests/insert.rs
index 7ca08228a1..1c8fc9e26a 100644
--- a/util/rich-indexer/src/tests/insert.rs
+++ b/util/rich-indexer/src/tests/insert.rs
@@ -1,3 +1,5 @@
+use std::usize;
+
 use super::*;
 
 use ckb_types::{
@@ -76,7 +78,7 @@ async fn with_custom_block_filter() {
             None,
         ),
     );
-    let indexer_handle = AsyncRichIndexerHandle::new(storage, None);
+    let indexer_handle = AsyncRichIndexerHandle::new(storage, None, usize::MAX);
 
     let lock_script1 = ScriptBuilder::default()
         .code_hash(H256(rand::random()).pack())
@@ -290,7 +292,7 @@ async fn with_custom_cell_filter() {
             Some(r#"output.type?.args == "0x747970655f73637269707431""#),
         ),
     );
-    let indexer_handle = AsyncRichIndexerHandle::new(storage, None);
+    let indexer_handle = AsyncRichIndexerHandle::new(storage, None, usize::MAX);
 
     let lock_script1 = ScriptBuilder::default()
         .code_hash(H256(rand::random()).pack())
diff --git a/util/rich-indexer/src/tests/query.rs b/util/rich-indexer/src/tests/query.rs
index 80731de569..8adcba4aeb 100644
--- a/util/rich-indexer/src/tests/query.rs
+++ b/util/rich-indexer/src/tests/query.rs
@@ -12,13 +12,16 @@ use ckb_types::{
     H256,
 };
 
-use std::sync::{Arc, RwLock};
+use std::{
+    sync::{Arc, RwLock},
+    usize,
+};
 use tokio::test;
 
 #[test]
 async fn test_query_tip() {
     let pool = connect_sqlite(MEMORY_DB).await;
-    let indexer = AsyncRichIndexerHandle::new(pool.clone(), None);
+    let indexer = AsyncRichIndexerHandle::new(pool.clone(), None, usize::MAX);
     let res = indexer.get_indexer_tip().await.unwrap();
     assert!(res.is_none());
 
@@ -34,7 +37,7 @@ async fn test_query_tip() {
 #[test]
 async fn get_cells() {
     let pool = connect_sqlite(MEMORY_DB).await;
-    let indexer = AsyncRichIndexerHandle::new(pool.clone(), None);
+    let indexer = AsyncRichIndexerHandle::new(pool.clone(), None, usize::MAX);
     let res = indexer.get_indexer_tip().await.unwrap();
     assert!(res.is_none());
 
@@ -163,7 +166,7 @@ async fn get_cells() {
 #[test]
 async fn get_cells_filter_data() {
     let pool = connect_sqlite(MEMORY_DB).await;
-    let indexer = AsyncRichIndexerHandle::new(pool.clone(), None);
+    let indexer = AsyncRichIndexerHandle::new(pool.clone(), None, usize::MAX);
     let res = indexer.get_indexer_tip().await.unwrap();
     assert!(res.is_none());
 
@@ -219,7 +222,7 @@ async fn get_cells_filter_data() {
 #[test]
 async fn get_cells_by_cursor() {
     let pool = connect_sqlite(MEMORY_DB).await;
-    let indexer = AsyncRichIndexerHandle::new(pool.clone(), None);
+    let indexer = AsyncRichIndexerHandle::new(pool.clone(), None, usize::MAX);
     let res = indexer.get_indexer_tip().await.unwrap();
     assert!(res.is_none());
 
@@ -281,7 +284,7 @@ async fn get_cells_by_cursor() {
 #[test]
 async fn get_transactions_ungrouped() {
     let pool = connect_sqlite(MEMORY_DB).await;
-    let indexer = AsyncRichIndexerHandle::new(pool.clone(), None);
+    let indexer = AsyncRichIndexerHandle::new(pool.clone(), None, usize::MAX);
 
     insert_blocks(pool).await;
 
@@ -405,7 +408,7 @@ async fn get_transactions_ungrouped() {
 #[test]
 async fn get_transactions_grouped() {
     let pool = connect_sqlite(MEMORY_DB).await;
-    let indexer = AsyncRichIndexerHandle::new(pool.clone(), None);
+    let indexer = AsyncRichIndexerHandle::new(pool.clone(), None, usize::MAX);
 
     insert_blocks(pool).await;
 
@@ -486,7 +489,7 @@ async fn get_transactions_grouped() {
 #[test]
 async fn get_cells_capacity() {
     let pool = connect_sqlite(MEMORY_DB).await;
-    let indexer = AsyncRichIndexerHandle::new(pool.clone(), None);
+    let indexer = AsyncRichIndexerHandle::new(pool.clone(), None, usize::MAX);
 
     insert_blocks(pool).await;
 
@@ -557,7 +560,7 @@ async fn rpc() {
     let store = connect_sqlite(MEMORY_DB).await;
     let pool = Arc::new(RwLock::new(Pool::default()));
     let indexer = AsyncRichIndexer::new(store.clone(), None, CustomFilters::new(None, None));
-    let rpc = AsyncRichIndexerHandle::new(store, Some(Arc::clone(&pool)));
+    let rpc = AsyncRichIndexerHandle::new(store, Some(Arc::clone(&pool)), usize::MAX);
 
     // setup test data
     let lock_script1 = ScriptBuilder::default()
@@ -1165,7 +1168,7 @@ async fn rpc() {
 async fn script_search_mode_rpc() {
     let pool = connect_sqlite(MEMORY_DB).await;
     let indexer = AsyncRichIndexer::new(pool.clone(), None, CustomFilters::new(None, None));
-    let rpc = AsyncRichIndexerHandle::new(pool, None);
+    let rpc = AsyncRichIndexerHandle::new(pool, None, usize::MAX);
 
     // setup test data
     let lock_script1 = ScriptBuilder::default()
@@ -1414,7 +1417,7 @@ async fn script_search_mode_rpc() {
 async fn output_data_filter_mode_rpc() {
     let pool = connect_sqlite(MEMORY_DB).await;
     let indexer = AsyncRichIndexer::new(pool.clone(), None, CustomFilters::new(None, None));
-    let rpc = AsyncRichIndexerHandle::new(pool, None);
+    let rpc = AsyncRichIndexerHandle::new(pool, None, usize::MAX);
 
     // setup test data
     let lock_script1 = ScriptBuilder::default()