ckb_block_filter/
filter.rs1use ckb_async_runtime::tokio::{self, task::block_in_place};
2use ckb_logger::{debug, info, warn};
3use ckb_shared::Shared;
4use ckb_stop_handler::{CancellationToken, new_tokio_exit_rx};
5use ckb_store::{ChainDB, ChainStore};
6use ckb_types::{
7 core::HeaderView,
8 packed::{Byte32, CellOutput, OutPoint},
9 utilities::{FilterDataProvider, build_filter_data},
10};
11
12const NAME: &str = "BlockFilter";
13
14#[derive(Clone)]
16pub struct BlockFilter {
17 shared: Shared,
18}
19
20struct WrappedChainDB<'a> {
21 inner: &'a ChainDB,
22}
23
24impl<'a> FilterDataProvider for WrappedChainDB<'a> {
25 fn cell(&self, out_point: &OutPoint) -> Option<CellOutput> {
26 self.inner
27 .get_transaction(&out_point.tx_hash())
28 .and_then(|(tx, _)| tx.outputs().get(out_point.index().into()))
29 }
30}
31
32impl<'a> WrappedChainDB<'a> {
33 fn new(inner: &'a ChainDB) -> Self {
34 Self { inner }
35 }
36}
37
38impl BlockFilter {
39 pub fn new(shared: Shared) -> Self {
41 Self { shared }
42 }
43
44 pub fn start(self) {
46 let notify_controller = self.shared.notify_controller().clone();
47 let async_handle = self.shared.async_handle().clone();
48 let stop_rx: CancellationToken = new_tokio_exit_rx();
49 let filter_data_builder = self.clone();
50
51 let build_filter_data =
52 async_handle.spawn_blocking(move || filter_data_builder.build_filter_data());
53
54 async_handle.spawn(async move {
55 let mut new_block_watcher = notify_controller.watch_new_block(NAME.to_string()).await;
56 let _build_filter_data_finished = build_filter_data.await;
57
58 loop {
59 tokio::select! {
60 Ok(_) = new_block_watcher.changed() => {
61 block_in_place(|| self.build_filter_data());
62 new_block_watcher.borrow_and_update();
63 }
64 _ = stop_rx.cancelled() => {
65 info!("BlockFilter received exit signal, exit now");
66 break
67 },
68 else => break,
69 }
70 }
71 });
72 }
73
74 fn build_filter_data(&self) {
76 let snapshot = self.shared.snapshot();
77 let tip_header = snapshot.get_tip_header().expect("tip stored");
78 let start_number = match snapshot.get_latest_built_filter_data_block_hash() {
79 Some(block_hash) => {
80 debug!("Hash of the latest created block {:#x}", block_hash);
81 if snapshot.is_main_chain(&block_hash) {
82 let header = snapshot
83 .get_block_header(&block_hash)
84 .expect("header stored");
85 debug!(
86 "Latest created block on the main chain, starting from {}",
87 header.number() + 1
88 );
89 header.number() + 1
90 } else {
91 let mut header = snapshot
93 .get_block_header(&block_hash)
94 .expect("header stored");
95 while !snapshot.is_main_chain(&header.parent_hash()) {
96 header = snapshot
97 .get_block_header(&header.parent_hash())
98 .expect("parent header stored");
99 }
100 debug!(
101 "Block with the latest built filter data on the forked chain, starting from {}",
102 header.number()
103 );
104 header.number()
105 }
106 }
107 None => 0,
108 };
109
110 for block_number in start_number..=tip_header.number() {
111 if ckb_stop_handler::has_received_stop_signal() {
112 info!("ckb has received stop signal, BlockFilter exit now");
113 return;
114 }
115
116 let block_hash = snapshot.get_block_hash(block_number).expect("index stored");
117 let header = snapshot
118 .get_block_header(&block_hash)
119 .expect("header stored");
120 self.build_filter_data_for_block(&header);
121 }
122 }
123
124 fn build_filter_data_for_block(&self, header: &HeaderView) {
125 debug!(
126 "Start building filter data for block: {}, hash: {:#x}",
127 header.number(),
128 header.hash()
129 );
130 let db = self.shared.store();
131 if db.get_block_filter_hash(&header.hash()).is_some() {
132 debug!(
133 "Filter data for block {:#x} already exists. Skip building.",
134 header.hash()
135 );
136 return;
137 }
138 let parent_block_filter_hash = if header.is_genesis() {
139 Byte32::zero()
140 } else {
141 db.get_block_filter_hash(&header.parent_hash())
142 .expect("parent block filter data stored")
143 };
144
145 let transactions = db.get_block_body(&header.hash());
146 let transactions_size: usize = transactions.iter().map(|tx| tx.data().total_size()).sum();
147 let provider = WrappedChainDB::new(db);
148 let (filter_data, missing_out_points) = build_filter_data(provider, &transactions);
149 for out_point in missing_out_points {
150 warn!(
151 "Unable to find the input cell for the out_point: {:#x}, \
152 Skip adding it to the filter. This should only happen during testing.",
153 out_point
154 );
155 }
156 let db_transaction = db.begin_transaction();
157 db_transaction
158 .insert_block_filter(
159 &header.hash(),
160 &(filter_data.clone().into()),
161 &parent_block_filter_hash,
162 )
163 .expect("insert_block_filter should be ok");
164 db_transaction.commit().expect("commit should be ok");
165 debug!(
166 "Inserted filter data for block: {}, hash: {:#x}, filter data size: {}, transactions size: {}",
167 header.number(),
168 header.hash(),
169 filter_data.len(),
170 transactions_size
171 );
172 }
173}