From ca2b119e8ffd9c500d193d1139310d72a77d67ce Mon Sep 17 00:00:00 2001
From: Charpa <jbcaron.dev@gmail.com>
Date: Tue, 21 Jan 2025 09:52:50 +0100
Subject: [PATCH] feat: store event bloom filter in DB

---
 Cargo.lock                                    | 856 +++++++++++++++---
 Cargo.toml                                    |   4 +
 crates/madara/client/block_import/Cargo.toml  |   1 +
 .../client/block_import/src/verify_apply.rs   |  23 +-
 .../madara/client/block_production/src/lib.rs |   1 +
 crates/madara/client/db/Cargo.toml            |   1 +
 crates/madara/client/db/src/block_db.rs       |  33 +-
 crates/madara/client/db/src/lib.rs            |  10 +
 .../madara/client/db/src/storage_updates.rs   |   4 +-
 .../madara/client/db/src/tests/test_block.rs  |  26 +-
 crates/madara/client/mempool/src/lib.rs       |   2 +
 crates/madara/client/rpc/Cargo.toml           |   1 +
 crates/madara/client/rpc/src/test_utils.rs    |   8 +
 crates/madara/client/rpc/src/utils/mod.rs     |  14 +
 .../methods/read/block_hash_and_number.rs     |   4 +
 .../methods/read/get_block_with_receipts.rs   |   1 +
 .../user/v0_7_1/methods/read/get_events.rs    |  36 +-
 .../v0_8_0/methods/ws/subscribe_events.rs     |   1 +
 .../v0_8_0/methods/ws/subscribe_new_heads.rs  |   1 +
 .../madara/primitives/bloom_filter/Cargo.toml |  43 +
 .../bloom_filter/benches/bloom_benchmark.rs   | 192 ++++
 .../primitives/bloom_filter/src/constants.rs  |   6 +
 .../primitives/bloom_filter/src/error.rs      |  11 +
 .../primitives/bloom_filter/src/events.rs     | 143 +++
 .../primitives/bloom_filter/src/filter.rs     | 272 ++++++
 .../madara/primitives/bloom_filter/src/lib.rs |  17 +
 .../primitives/bloom_filter/src/storage.rs    | 204 +++++
 .../bloom_filter/src/tests/filter_tests.rs    | 191 ++++
 .../primitives/bloom_filter/src/tests/mod.rs  |   6 +
 .../bloom_filter/src/tests/serialization.rs   |  88 ++
 .../bloom_filter/src/tests/utils.rs           |  20 +
 31 files changed, 2087 insertions(+), 133 deletions(-)
 create mode 100644 crates/madara/primitives/bloom_filter/Cargo.toml
 create mode 100644 crates/madara/primitives/bloom_filter/benches/bloom_benchmark.rs
 create mode 100644 crates/madara/primitives/bloom_filter/src/constants.rs
 create mode 100644 crates/madara/primitives/bloom_filter/src/error.rs
 create mode 100644 crates/madara/primitives/bloom_filter/src/events.rs
 create mode 100644 crates/madara/primitives/bloom_filter/src/filter.rs
 create mode 100644 crates/madara/primitives/bloom_filter/src/lib.rs
 create mode 100644 crates/madara/primitives/bloom_filter/src/storage.rs
 create mode 100644 crates/madara/primitives/bloom_filter/src/tests/filter_tests.rs
 create mode 100644 crates/madara/primitives/bloom_filter/src/tests/mod.rs
 create mode 100644 crates/madara/primitives/bloom_filter/src/tests/serialization.rs
 create mode 100644 crates/madara/primitives/bloom_filter/src/tests/utils.rs

diff --git a/Cargo.lock b/Cargo.lock
index 50ce03c38..cb1794961 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -35,6 +35,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
 dependencies = [
  "cfg-if",
+ "getrandom",
  "once_cell",
  "version_check",
  "zerocopy",
@@ -273,7 +274,7 @@ dependencies = [
  "alloy-genesis",
  "alloy-primitives",
  "k256",
- "rand",
+ "rand 0.8.5",
  "serde_json",
  "tempfile",
  "thiserror 1.0.65",
@@ -301,7 +302,7 @@ dependencies = [
  "keccak-asm",
  "paste",
  "proptest",
- "rand",
+ "rand 0.8.5",
  "ruint",
  "rustc-hash 2.0.0",
  "serde",
@@ -469,7 +470,7 @@ dependencies = [
  "alloy-signer",
  "async-trait",
  "k256",
- "rand",
+ "rand 0.8.5",
  "thiserror 1.0.65",
 ]
 
@@ -595,6 +596,12 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "anes"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
+
 [[package]]
 name = "anstream"
 version = "0.6.17"
@@ -684,7 +691,7 @@ dependencies = [
  "ark-serialize 0.3.0",
  "ark-std 0.3.0",
  "derivative",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "paste",
  "rustc_version 0.3.3",
@@ -704,7 +711,7 @@ dependencies = [
  "derivative",
  "digest 0.10.7",
  "itertools 0.10.5",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "paste",
  "rustc_version 0.4.1",
@@ -737,7 +744,7 @@ version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20"
 dependencies = [
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "quote",
  "syn 1.0.109",
@@ -749,7 +756,7 @@ version = "0.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565"
 dependencies = [
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "proc-macro2",
  "quote",
@@ -810,7 +817,7 @@ dependencies = [
  "ark-serialize-derive",
  "ark-std 0.4.0",
  "digest 0.10.7",
- "num-bigint",
+ "num-bigint 0.4.6",
 ]
 
 [[package]]
@@ -831,7 +838,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c"
 dependencies = [
  "num-traits 0.2.19",
- "rand",
+ "rand 0.8.5",
 ]
 
 [[package]]
@@ -841,7 +848,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185"
 dependencies = [
  "num-traits 0.2.19",
- "rand",
+ "rand 0.8.5",
 ]
 
 [[package]]
@@ -1120,6 +1127,15 @@ dependencies = [
  "syn 2.0.89",
 ]
 
+[[package]]
+name = "autocfg"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78"
+dependencies = [
+ "autocfg 1.4.0",
+]
+
 [[package]]
 name = "autocfg"
 version = "1.4.0"
@@ -1244,9 +1260,9 @@ version = "0.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "51d712318a27c7150326677b321a5fa91b55f6d9034ffd67f20319e147d40cee"
 dependencies = [
- "autocfg",
+ "autocfg 1.4.0",
  "libm",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-integer",
  "num-traits 0.2.19",
 ]
@@ -1368,14 +1384,14 @@ dependencies = [
  "itertools 0.10.5",
  "keccak",
  "log",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-integer",
- "num-rational",
+ "num-rational 0.4.2",
  "num-traits 0.2.19",
  "once_cell",
  "paste",
  "phf",
- "rand",
+ "rand 0.8.5",
  "rstest 0.17.0",
  "serde",
  "serde_json",
@@ -1453,6 +1469,12 @@ version = "1.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c"
 
+[[package]]
+name = "bytemuck"
+version = "1.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3"
+
 [[package]]
 name = "byteorder"
 version = "1.5.0"
@@ -1547,7 +1569,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "250f460db3bb8e8589812495fdca7301e9674b3a2c81f2380e9c07d914979a42"
 dependencies = [
  "lazy_static",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-integer",
  "num-traits 0.2.19",
  "serde",
@@ -1560,7 +1582,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a93dedd19b8edf685798f1f12e4e0ac21ac196ea5262c300783f69f3fa0cb28b"
 dependencies = [
  "lazy_static",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-integer",
  "num-traits 0.2.19",
  "serde",
@@ -1573,7 +1595,7 @@ source = "git+https://github.com/starkware-libs/cairo?tag=v1.0.0-alpha.6#439da05
 dependencies = [
  "cairo-lang-utils 1.0.0-alpha.6",
  "indoc 1.0.9",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "serde",
  "thiserror 1.0.65",
@@ -1586,7 +1608,7 @@ source = "git+https://github.com/starkware-libs/cairo?tag=v1.0.0-rc0#05867c82de4
 dependencies = [
  "cairo-lang-utils 1.0.0-rc0",
  "indoc 2.0.5",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "serde",
  "thiserror 1.0.65",
@@ -1600,7 +1622,7 @@ checksum = "076a07a68b7f4b3f04e0e23f1e4bee42358abab54929b7842b42108bdb76a164"
 dependencies = [
  "cairo-lang-utils 1.1.1",
  "indoc 2.0.5",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "serde",
  "thiserror 1.0.65",
@@ -1614,7 +1636,7 @@ checksum = "fd4d6659539ace9649c8e8a7434e51b0c50a7a700111d0a2b967dde220ddff49"
 dependencies = [
  "cairo-lang-utils 2.8.4",
  "indoc 2.0.5",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "parity-scale-codec",
  "serde",
@@ -1998,7 +2020,7 @@ dependencies = [
  "indexmap 1.9.3",
  "itertools 0.10.5",
  "log",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "salsa",
  "smol_str 0.1.24",
@@ -2022,7 +2044,7 @@ dependencies = [
  "indexmap 1.9.3",
  "itertools 0.10.5",
  "log",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "salsa",
  "smol_str 0.2.2",
@@ -2047,7 +2069,7 @@ dependencies = [
  "indexmap 1.9.3",
  "itertools 0.10.5",
  "log",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "salsa",
  "smol_str 0.2.2",
@@ -2071,7 +2093,7 @@ dependencies = [
  "id-arena",
  "itertools 0.12.1",
  "log",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "rust-analyzer-salsa",
  "smol_str 0.2.2",
@@ -2107,7 +2129,7 @@ dependencies = [
  "colored",
  "itertools 0.10.5",
  "log",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "salsa",
  "smol_str 0.2.2",
@@ -2128,7 +2150,7 @@ dependencies = [
  "colored",
  "itertools 0.10.5",
  "log",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "salsa",
  "smol_str 0.2.2",
@@ -2148,7 +2170,7 @@ dependencies = [
  "cairo-lang-utils 2.8.4",
  "colored",
  "itertools 0.12.1",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "rust-analyzer-salsa",
  "smol_str 0.2.2",
@@ -2344,10 +2366,10 @@ dependencies = [
  "cairo-vm",
  "itertools 0.12.1",
  "keccak",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-integer",
  "num-traits 0.2.19",
- "rand",
+ "rand 0.8.5",
  "sha2",
  "smol_str 0.2.2",
  "starknet-types-core 0.1.7 (git+https://github.com/kasarlabs/types-rs.git?branch=feat-deserialize-v0.1.7)",
@@ -2370,7 +2392,7 @@ dependencies = [
  "id-arena",
  "itertools 0.10.5",
  "log",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "pretty_assertions",
  "salsa",
@@ -2394,7 +2416,7 @@ dependencies = [
  "id-arena",
  "itertools 0.10.5",
  "log",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "salsa",
  "smol_str 0.2.2",
@@ -2417,7 +2439,7 @@ dependencies = [
  "id-arena",
  "itertools 0.10.5",
  "log",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "salsa",
  "smol_str 0.2.2",
@@ -2442,7 +2464,7 @@ dependencies = [
  "id-arena",
  "indoc 2.0.5",
  "itertools 0.12.1",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "rust-analyzer-salsa",
  "smol_str 0.2.2",
@@ -2461,7 +2483,7 @@ dependencies = [
  "itertools 0.10.5",
  "lalrpop 0.19.12",
  "lalrpop-util 0.19.12",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "regex",
  "salsa",
@@ -2483,7 +2505,7 @@ dependencies = [
  "itertools 0.10.5",
  "lalrpop 0.19.12",
  "lalrpop-util 0.19.12",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "regex",
  "salsa",
@@ -2506,7 +2528,7 @@ dependencies = [
  "itertools 0.10.5",
  "lalrpop 0.19.12",
  "lalrpop-util 0.19.12",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "regex",
  "salsa",
@@ -2530,7 +2552,7 @@ dependencies = [
  "itertools 0.12.1",
  "lalrpop 0.20.2",
  "lalrpop-util 0.20.2",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-integer",
  "num-traits 0.2.19",
  "regex",
@@ -2591,7 +2613,7 @@ dependencies = [
  "cairo-lang-sierra-type-size",
  "cairo-lang-utils 2.8.4",
  "itertools 0.12.1",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "thiserror 1.0.65",
 ]
@@ -2644,7 +2666,7 @@ dependencies = [
  "cairo-lang-sierra-type-size",
  "cairo-lang-utils 2.8.4",
  "itertools 0.12.1",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "thiserror 1.0.65",
 ]
@@ -2669,7 +2691,7 @@ dependencies = [
  "id-arena",
  "indexmap 1.9.3",
  "itertools 0.10.5",
- "num-bigint",
+ "num-bigint 0.4.6",
  "salsa",
  "smol_str 0.1.24",
 ]
@@ -2694,7 +2716,7 @@ dependencies = [
  "id-arena",
  "indexmap 1.9.3",
  "itertools 0.10.5",
- "num-bigint",
+ "num-bigint 0.4.6",
  "salsa",
  "smol_str 0.2.2",
 ]
@@ -2720,7 +2742,7 @@ dependencies = [
  "id-arena",
  "indexmap 1.9.3",
  "itertools 0.10.5",
- "num-bigint",
+ "num-bigint 0.4.6",
  "salsa",
  "smol_str 0.2.2",
 ]
@@ -2766,7 +2788,7 @@ dependencies = [
  "indoc 1.0.9",
  "itertools 0.10.5",
  "log",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "thiserror 1.0.65",
 ]
@@ -2788,7 +2810,7 @@ dependencies = [
  "indoc 2.0.5",
  "itertools 0.10.5",
  "log",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "thiserror 1.0.65",
 ]
@@ -2811,7 +2833,7 @@ dependencies = [
  "indoc 2.0.5",
  "itertools 0.10.5",
  "log",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "thiserror 1.0.65",
 ]
@@ -2831,7 +2853,7 @@ dependencies = [
  "cairo-lang-utils 2.8.4",
  "indoc 2.0.5",
  "itertools 0.12.1",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "starknet-types-core 0.1.7 (git+https://github.com/kasarlabs/types-rs.git?branch=feat-deserialize-v0.1.7)",
  "thiserror 1.0.65",
@@ -2876,7 +2898,7 @@ dependencies = [
  "itertools 0.10.5",
  "lazy_static",
  "log",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-integer",
  "num-traits 0.2.19",
  "serde",
@@ -2915,7 +2937,7 @@ dependencies = [
  "indoc 2.0.5",
  "itertools 0.10.5",
  "log",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-integer",
  "num-traits 0.2.19",
  "once_cell",
@@ -2956,7 +2978,7 @@ dependencies = [
  "indoc 2.0.5",
  "itertools 0.10.5",
  "log",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-integer",
  "num-traits 0.2.19",
  "once_cell",
@@ -3009,7 +3031,7 @@ dependencies = [
  "cairo-lang-utils 2.8.4",
  "convert_case 0.6.0",
  "itertools 0.12.1",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-integer",
  "num-traits 0.2.19",
  "serde",
@@ -3040,7 +3062,7 @@ dependencies = [
  "cairo-lang-debug 1.0.0-rc0",
  "cairo-lang-filesystem 1.0.0-rc0",
  "cairo-lang-utils 1.0.0-rc0",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "salsa",
  "smol_str 0.2.2",
@@ -3057,7 +3079,7 @@ dependencies = [
  "cairo-lang-debug 1.1.1",
  "cairo-lang-filesystem 1.1.1",
  "cairo-lang-utils 1.1.1",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "salsa",
  "smol_str 0.2.2",
@@ -3074,7 +3096,7 @@ dependencies = [
  "cairo-lang-debug 2.8.4",
  "cairo-lang-filesystem 2.8.4",
  "cairo-lang-utils 2.8.4",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "rust-analyzer-salsa",
  "smol_str 0.2.2",
@@ -3148,7 +3170,7 @@ dependencies = [
  "indexmap 1.9.3",
  "itertools 0.10.5",
  "log",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-integer",
  "num-traits 0.2.19",
  "serde",
@@ -3163,7 +3185,7 @@ dependencies = [
  "indexmap 1.9.3",
  "itertools 0.10.5",
  "log",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-integer",
  "num-traits 0.2.19",
  "serde",
@@ -3180,7 +3202,7 @@ dependencies = [
  "indexmap 1.9.3",
  "itertools 0.10.5",
  "log",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-integer",
  "num-traits 0.2.19",
  "serde",
@@ -3196,7 +3218,7 @@ dependencies = [
  "hashbrown 0.14.5",
  "indexmap 2.6.0",
  "itertools 0.12.1",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "parity-scale-codec",
  "schemars",
@@ -3218,11 +3240,11 @@ dependencies = [
  "keccak",
  "lazy_static",
  "nom",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-integer",
  "num-prime",
  "num-traits 0.2.19",
- "rand",
+ "rand 0.8.5",
  "rust_decimal",
  "serde",
  "serde_json",
@@ -3234,6 +3256,12 @@ dependencies = [
  "zip",
 ]
 
+[[package]]
+name = "cast"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
+
 [[package]]
 name = "cc"
 version = "1.1.31"
@@ -3275,6 +3303,33 @@ dependencies = [
  "windows-targets 0.52.6",
 ]
 
+[[package]]
+name = "ciborium"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
+dependencies = [
+ "ciborium-io",
+ "ciborium-ll",
+ "serde",
+]
+
+[[package]]
+name = "ciborium-io"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
+
+[[package]]
+name = "ciborium-ll"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
+dependencies = [
+ "ciborium-io",
+ "half",
+]
+
 [[package]]
 name = "cipher"
 version = "0.4.4"
@@ -3336,6 +3391,21 @@ version = "0.7.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
 
+[[package]]
+name = "cloudabi"
+version = "0.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
+[[package]]
+name = "color_quant"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
+
 [[package]]
 name = "colorchoice"
 version = "1.0.3"
@@ -3456,6 +3526,42 @@ version = "0.8.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
 
+[[package]]
+name = "core-graphics"
+version = "0.23.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "core-graphics-types",
+ "foreign-types 0.5.0",
+ "libc",
+]
+
+[[package]]
+name = "core-graphics-types"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf"
+dependencies = [
+ "bitflags 1.3.2",
+ "core-foundation",
+ "libc",
+]
+
+[[package]]
+name = "core-text"
+version = "20.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9d2790b5c08465d49f8dc05c8bcae9fea467855947db39b0f8145c091aaced5"
+dependencies = [
+ "core-foundation",
+ "core-graphics",
+ "foreign-types 0.5.0",
+ "libc",
+]
+
 [[package]]
 name = "cpufeatures"
 version = "0.2.14"
@@ -3474,6 +3580,42 @@ dependencies = [
  "cfg-if",
 ]
 
+[[package]]
+name = "criterion"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
+dependencies = [
+ "anes",
+ "cast",
+ "ciborium",
+ "clap",
+ "criterion-plot",
+ "is-terminal",
+ "itertools 0.10.5",
+ "num-traits 0.2.19",
+ "once_cell",
+ "oorandom",
+ "plotters",
+ "rayon",
+ "regex",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "tinytemplate",
+ "walkdir",
+]
+
+[[package]]
+name = "criterion-plot"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
+dependencies = [
+ "cast",
+ "itertools 0.10.5",
+]
+
 [[package]]
 name = "crossbeam-deque"
 version = "0.8.5"
@@ -3512,7 +3654,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
 dependencies = [
  "generic-array",
- "rand_core",
+ "rand_core 0.6.4",
  "subtle",
  "zeroize",
 ]
@@ -3727,6 +3869,15 @@ dependencies = [
  "subtle",
 ]
 
+[[package]]
+name = "dirs"
+version = "5.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
+dependencies = [
+ "dirs-sys",
+]
+
 [[package]]
 name = "dirs-next"
 version = "2.0.0"
@@ -3737,6 +3888,18 @@ dependencies = [
  "dirs-sys-next",
 ]
 
+[[package]]
+name = "dirs-sys"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
+dependencies = [
+ "libc",
+ "option-ext",
+ "redox_users",
+ "windows-sys 0.48.0",
+]
+
 [[package]]
 name = "dirs-sys-next"
 version = "0.1.2"
@@ -3748,6 +3911,15 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "dlib"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
+dependencies = [
+ "libloading",
+]
+
 [[package]]
 name = "dotenv"
 version = "0.15.0"
@@ -3766,6 +3938,18 @@ version = "1.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
 
+[[package]]
+name = "dwrote"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70182709525a3632b2ba96b6569225467b18ecb4a77f46d255f713a6bebf05fd"
+dependencies = [
+ "lazy_static",
+ "libc",
+ "winapi",
+ "wio",
+]
+
 [[package]]
 name = "dyn-clone"
 version = "1.0.17"
@@ -3805,7 +3989,7 @@ dependencies = [
  "generic-array",
  "group",
  "pkcs8",
- "rand_core",
+ "rand_core 0.6.4",
  "sec1",
  "subtle",
  "zeroize",
@@ -3876,7 +4060,7 @@ dependencies = [
  "hex",
  "hmac",
  "pbkdf2",
- "rand",
+ "rand 0.8.5",
  "scrypt",
  "serde",
  "serde_json",
@@ -3957,6 +4141,15 @@ dependencies = [
  "bytes",
 ]
 
+[[package]]
+name = "fdeflate"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
+dependencies = [
+ "simd-adler32",
+]
+
 [[package]]
 name = "fdlimit"
 version = "0.3.0"
@@ -3973,7 +4166,7 @@ version = "0.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449"
 dependencies = [
- "rand_core",
+ "rand_core 0.6.4",
  "subtle",
 ]
 
@@ -3984,7 +4177,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534"
 dependencies = [
  "byteorder",
- "rand",
+ "rand 0.8.5",
  "rustc-hex",
  "static_assertions",
 ]
@@ -4005,6 +4198,12 @@ dependencies = [
  "miniz_oxide",
 ]
 
+[[package]]
+name = "float-ord"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d"
+
 [[package]]
 name = "fnv"
 version = "1.0.7"
@@ -4017,13 +4216,59 @@ version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2"
 
+[[package]]
+name = "font-kit"
+version = "0.14.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b64b34f4efd515f905952d91bc185039863705592c0c53ae6d979805dd154520"
+dependencies = [
+ "bitflags 2.6.0",
+ "byteorder",
+ "core-foundation",
+ "core-graphics",
+ "core-text",
+ "dirs",
+ "dwrote",
+ "float-ord",
+ "freetype-sys",
+ "lazy_static",
+ "libc",
+ "log",
+ "pathfinder_geometry",
+ "pathfinder_simd",
+ "walkdir",
+ "winapi",
+ "yeslogic-fontconfig-sys",
+]
+
 [[package]]
 name = "foreign-types"
 version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
 dependencies = [
- "foreign-types-shared",
+ "foreign-types-shared 0.1.1",
+]
+
+[[package]]
+name = "foreign-types"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
+dependencies = [
+ "foreign-types-macros",
+ "foreign-types-shared 0.3.1",
+]
+
+[[package]]
+name = "foreign-types-macros"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.89",
 ]
 
 [[package]]
@@ -4032,6 +4277,12 @@ version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
 
+[[package]]
+name = "foreign-types-shared"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
+
 [[package]]
 name = "form_urlencoded"
 version = "1.2.1"
@@ -4047,6 +4298,23 @@ version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa"
 
+[[package]]
+name = "freetype-sys"
+version = "0.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e7edc5b9669349acfda99533e9e0bcf26a51862ab43b08ee7745c55d28eb134"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "fuchsia-cprng"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
+
 [[package]]
 name = "funty"
 version = "2.0.0"
@@ -4218,6 +4486,16 @@ dependencies = [
  "wasm-bindgen",
 ]
 
+[[package]]
+name = "gif"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045"
+dependencies = [
+ "color_quant",
+ "weezl",
+]
+
 [[package]]
 name = "gimli"
 version = "0.31.1"
@@ -4318,7 +4596,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
 dependencies = [
  "ff",
- "rand_core",
+ "rand_core 0.6.4",
  "subtle",
 ]
 
@@ -4360,6 +4638,16 @@ dependencies = [
  "tracing",
 ]
 
+[[package]]
+name = "half"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
+dependencies = [
+ "cfg-if",
+ "crunchy",
+]
+
 [[package]]
 name = "hashbrown"
 version = "0.12.3"
@@ -4760,6 +5048,20 @@ dependencies = [
  "winapi-util",
 ]
 
+[[package]]
+name = "image"
+version = "0.24.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d"
+dependencies = [
+ "bytemuck",
+ "byteorder",
+ "color_quant",
+ "jpeg-decoder",
+ "num-traits 0.2.19",
+ "png",
+]
+
 [[package]]
 name = "impl-codec"
 version = "0.6.0"
@@ -4810,7 +5112,7 @@ version = "1.9.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
 dependencies = [
- "autocfg",
+ "autocfg 1.4.0",
  "hashbrown 0.12.3",
  "serde",
 ]
@@ -4930,6 +5232,12 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "jpeg-decoder"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0"
+
 [[package]]
 name = "js-sys"
 version = "0.3.72"
@@ -4996,7 +5304,7 @@ dependencies = [
  "jsonrpsee-types",
  "parking_lot 0.12.3",
  "pin-project",
- "rand",
+ "rand 0.8.5",
  "rustc-hash 1.1.0",
  "serde",
  "serde_json",
@@ -5317,7 +5625,7 @@ version = "0.4.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
 dependencies = [
- "autocfg",
+ "autocfg 1.4.0",
  "scopeguard",
 ]
 
@@ -5400,7 +5708,7 @@ dependencies = [
  "opentelemetry-semantic-conventions",
  "opentelemetry-stdout",
  "opentelemetry_sdk",
- "rand",
+ "rand 0.8.5",
  "rayon",
  "reqwest 0.12.8",
  "serde",
@@ -5476,6 +5784,7 @@ dependencies = [
  "mc-analytics",
  "mc-db",
  "mp-block",
+ "mp-bloom-filter",
  "mp-chain-config",
  "mp-class",
  "mp-convert",
@@ -5560,6 +5869,7 @@ dependencies = [
  "librocksdb-sys",
  "mc-analytics",
  "mp-block",
+ "mp-bloom-filter",
  "mp-chain-config",
  "mp-class",
  "mp-receipt",
@@ -5842,6 +6152,7 @@ dependencies = [
  "mc-gateway-client",
  "mc-mempool",
  "mp-block",
+ "mp-bloom-filter",
  "mp-chain-config",
  "mp-class",
  "mp-convert",
@@ -5956,6 +6267,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
 dependencies = [
  "adler2",
+ "simd-adler32",
 ]
 
 [[package]]
@@ -6020,6 +6332,24 @@ dependencies = [
  "tracing-subscriber",
 ]
 
+[[package]]
+name = "mp-bloom-filter"
+version = "0.7.0"
+dependencies = [
+ "ahash",
+ "bincode 1.3.3",
+ "criterion",
+ "mp-receipt",
+ "plotters",
+ "rand 0.8.5",
+ "rayon",
+ "serde",
+ "starknet-types-core 0.1.7 (git+https://github.com/kasarlabs/types-rs.git?branch=feat-deserialize-v0.1.7)",
+ "statistical",
+ "thiserror 2.0.3",
+ "twox-hash",
+]
+
 [[package]]
 name = "mp-chain-config"
 version = "0.7.0"
@@ -6058,7 +6388,7 @@ dependencies = [
  "flate2",
  "lazy_static",
  "mp-convert",
- "num-bigint",
+ "num-bigint 0.4.6",
  "serde",
  "serde_json",
  "starknet-core",
@@ -6166,7 +6496,7 @@ dependencies = [
  "mp-chain-config",
  "mp-class",
  "mp-convert",
- "num-bigint",
+ "num-bigint 0.4.6",
  "serde",
  "serde_json",
  "serde_with",
@@ -6194,7 +6524,7 @@ dependencies = [
  "opentelemetry-stdout",
  "opentelemetry_sdk",
  "paste",
- "rand",
+ "rand 0.8.5",
  "rayon",
  "rstest 0.18.2",
  "serde",
@@ -6276,6 +6606,31 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "num"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36"
+dependencies = [
+ "num-bigint 0.2.6",
+ "num-complex",
+ "num-integer",
+ "num-iter",
+ "num-rational 0.2.4",
+ "num-traits 0.2.19",
+]
+
+[[package]]
+name = "num-bigint"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
+dependencies = [
+ "autocfg 1.4.0",
+ "num-integer",
+ "num-traits 0.2.19",
+]
+
 [[package]]
 name = "num-bigint"
 version = "0.4.6"
@@ -6284,7 +6639,7 @@ checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
 dependencies = [
  "num-integer",
  "num-traits 0.2.19",
- "rand",
+ "rand 0.8.5",
  "serde",
 ]
 
@@ -6294,7 +6649,7 @@ version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95"
 dependencies = [
- "autocfg",
+ "autocfg 1.4.0",
  "num-traits 0.2.19",
 ]
 
@@ -6313,13 +6668,24 @@ dependencies = [
  "num-traits 0.2.19",
 ]
 
+[[package]]
+name = "num-iter"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
+dependencies = [
+ "autocfg 1.4.0",
+ "num-integer",
+ "num-traits 0.2.19",
+]
+
 [[package]]
 name = "num-modular"
 version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "64a5fe11d4135c3bcdf3a95b18b194afa9608a5f6ff034f5d857bc9a27fb0119"
 dependencies = [
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-integer",
  "num-traits 0.2.19",
 ]
@@ -6333,11 +6699,23 @@ dependencies = [
  "bitvec",
  "either",
  "lru",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-integer",
  "num-modular",
  "num-traits 0.2.19",
- "rand",
+ "rand 0.8.5",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"
+dependencies = [
+ "autocfg 1.4.0",
+ "num-bigint 0.2.6",
+ "num-integer",
+ "num-traits 0.2.19",
 ]
 
 [[package]]
@@ -6346,7 +6724,7 @@ version = "0.4.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
 dependencies = [
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-integer",
  "num-traits 0.2.19",
  "serde",
@@ -6367,7 +6745,7 @@ version = "0.2.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
 dependencies = [
- "autocfg",
+ "autocfg 1.4.0",
  "libm",
 ]
 
@@ -6445,7 +6823,7 @@ checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5"
 dependencies = [
  "bitflags 2.6.0",
  "cfg-if",
- "foreign-types",
+ "foreign-types 0.3.2",
  "libc",
  "once_cell",
  "openssl-macros",
@@ -6574,13 +6952,19 @@ dependencies = [
  "once_cell",
  "opentelemetry",
  "percent-encoding",
- "rand",
+ "rand 0.8.5",
  "serde_json",
  "thiserror 1.0.65",
  "tokio",
  "tokio-stream",
 ]
 
+[[package]]
+name = "option-ext"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
+
 [[package]]
 name = "ordered-float"
 version = "4.4.0"
@@ -6683,7 +7067,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
 dependencies = [
  "base64ct",
- "rand_core",
+ "rand_core 0.6.4",
  "subtle",
 ]
 
@@ -6705,6 +7089,25 @@ version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "17359afc20d7ab31fdb42bb844c8b3bb1dabd7dcf7e68428492da7f16966fcef"
 
+[[package]]
+name = "pathfinder_geometry"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3"
+dependencies = [
+ "log",
+ "pathfinder_simd",
+]
+
+[[package]]
+name = "pathfinder_simd"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cf07ef4804cfa9aea3b04a7bbdd5a40031dbb6b4f2cbaf2b011666c80c5b4f2"
+dependencies = [
+ "rustc_version 0.4.1",
+]
+
 [[package]]
 name = "pbkdf2"
 version = "0.11.0"
@@ -6761,7 +7164,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
 dependencies = [
  "phf_shared 0.11.2",
- "rand",
+ "rand 0.8.5",
 ]
 
 [[package]]
@@ -6860,6 +7263,65 @@ version = "0.3.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
 
+[[package]]
+name = "plotters"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
+dependencies = [
+ "chrono",
+ "font-kit",
+ "image",
+ "lazy_static",
+ "num-traits 0.2.19",
+ "pathfinder_geometry",
+ "plotters-backend",
+ "plotters-bitmap",
+ "plotters-svg",
+ "ttf-parser",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "plotters-backend"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
+
+[[package]]
+name = "plotters-bitmap"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72ce181e3f6bf82d6c1dc569103ca7b1bd964c60ba03d7e6cdfbb3e3eb7f7405"
+dependencies = [
+ "gif",
+ "image",
+ "plotters-backend",
+]
+
+[[package]]
+name = "plotters-svg"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
+dependencies = [
+ "plotters-backend",
+]
+
+[[package]]
+name = "png"
+version = "0.17.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
+dependencies = [
+ "bitflags 1.3.2",
+ "crc32fast",
+ "fdeflate",
+ "flate2",
+ "miniz_oxide",
+]
+
 [[package]]
 name = "polling"
 version = "3.7.3"
@@ -6996,9 +7458,9 @@ dependencies = [
  "bitflags 2.6.0",
  "lazy_static",
  "num-traits 0.2.19",
- "rand",
- "rand_chacha",
- "rand_xorshift",
+ "rand 0.8.5",
+ "rand_chacha 0.3.1",
+ "rand_xorshift 0.3.0",
  "regex-syntax 0.8.5",
  "rusty-fork",
  "tempfile",
@@ -7069,6 +7531,25 @@ version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
 
+[[package]]
+name = "rand"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
+dependencies = [
+ "autocfg 0.1.8",
+ "libc",
+ "rand_chacha 0.1.1",
+ "rand_core 0.4.2",
+ "rand_hc",
+ "rand_isaac",
+ "rand_jitter",
+ "rand_os",
+ "rand_pcg",
+ "rand_xorshift 0.1.1",
+ "winapi",
+]
+
 [[package]]
 name = "rand"
 version = "0.8.5"
@@ -7076,11 +7557,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
 dependencies = [
  "libc",
- "rand_chacha",
- "rand_core",
+ "rand_chacha 0.3.1",
+ "rand_core 0.6.4",
  "serde",
 ]
 
+[[package]]
+name = "rand_chacha"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
+dependencies = [
+ "autocfg 0.1.8",
+ "rand_core 0.3.1",
+]
+
 [[package]]
 name = "rand_chacha"
 version = "0.3.1"
@@ -7088,9 +7579,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
 dependencies = [
  "ppv-lite86",
- "rand_core",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
+dependencies = [
+ "rand_core 0.4.2",
 ]
 
+[[package]]
+name = "rand_core"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
+
 [[package]]
 name = "rand_core"
 version = "0.6.4"
@@ -7100,13 +7606,75 @@ dependencies = [
  "getrandom",
 ]
 
+[[package]]
+name = "rand_hc"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
+dependencies = [
+ "rand_core 0.3.1",
+]
+
+[[package]]
+name = "rand_isaac"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
+dependencies = [
+ "rand_core 0.3.1",
+]
+
+[[package]]
+name = "rand_jitter"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
+dependencies = [
+ "libc",
+ "rand_core 0.4.2",
+ "winapi",
+]
+
+[[package]]
+name = "rand_os"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
+dependencies = [
+ "cloudabi",
+ "fuchsia-cprng",
+ "libc",
+ "rand_core 0.4.2",
+ "rdrand",
+ "winapi",
+]
+
+[[package]]
+name = "rand_pcg"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
+dependencies = [
+ "autocfg 0.1.8",
+ "rand_core 0.4.2",
+]
+
+[[package]]
+name = "rand_xorshift"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
+dependencies = [
+ "rand_core 0.3.1",
+]
+
 [[package]]
 name = "rand_xorshift"
 version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f"
 dependencies = [
- "rand_core",
+ "rand_core 0.6.4",
 ]
 
 [[package]]
@@ -7135,6 +7703,15 @@ dependencies = [
  "crossbeam-utils",
 ]
 
+[[package]]
+name = "rdrand"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
+dependencies = [
+ "rand_core 0.3.1",
+]
+
 [[package]]
 name = "redox_syscall"
 version = "0.2.16"
@@ -7432,12 +8009,12 @@ dependencies = [
  "ark-ff 0.4.2",
  "bytes",
  "fastrlp",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-traits 0.2.19",
  "parity-scale-codec",
  "primitive-types",
  "proptest",
- "rand",
+ "rand 0.8.5",
  "rlp",
  "ruint-macro",
  "serde",
@@ -8071,9 +8648,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
 dependencies = [
  "digest 0.10.7",
- "rand_core",
+ "rand_core 0.6.4",
 ]
 
+[[package]]
+name = "simd-adler32"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
+
 [[package]]
 name = "similar"
 version = "2.6.0"
@@ -8092,7 +8675,7 @@ version = "0.4.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
 dependencies = [
- "autocfg",
+ "autocfg 1.4.0",
 ]
 
 [[package]]
@@ -8153,7 +8736,7 @@ dependencies = [
  "http 0.2.12",
  "httparse",
  "log",
- "rand",
+ "rand 0.8.5",
  "sha-1",
 ]
 
@@ -8262,7 +8845,7 @@ dependencies = [
  "crypto-bigint",
  "hex",
  "hmac",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-integer",
  "num-traits 0.2.19",
  "rfc6979",
@@ -8282,7 +8865,7 @@ dependencies = [
  "crypto-bigint",
  "hex",
  "hmac",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-integer",
  "num-traits 0.2.19",
  "rfc6979",
@@ -8302,7 +8885,7 @@ dependencies = [
  "crypto-bigint",
  "hex",
  "hmac",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-integer",
  "num-traits 0.2.19",
  "rfc6979",
@@ -8320,7 +8903,7 @@ dependencies = [
  "crypto-bigint",
  "hex",
  "hmac",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-integer",
  "num-traits 0.2.19",
  "rfc6979",
@@ -8430,7 +9013,7 @@ dependencies = [
  "crypto-bigint",
  "eth-keystore",
  "getrandom",
- "rand",
+ "rand 0.8.5",
  "starknet-core",
  "starknet-crypto 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "thiserror 1.0.65",
@@ -8445,7 +9028,7 @@ dependencies = [
  "lambdaworks-crypto",
  "lambdaworks-math",
  "lazy_static",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-integer",
  "num-traits 0.2.19",
  "parity-scale-codec",
@@ -8459,7 +9042,7 @@ source = "git+https://github.com/jbcaron/types-rs.git?branch=fork#570eb3eb21d64a
 dependencies = [
  "lambdaworks-crypto",
  "lambdaworks-math",
- "num-bigint",
+ "num-bigint 0.4.6",
  "num-integer",
  "num-traits 0.2.19",
  "serde",
@@ -8504,6 +9087,16 @@ version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
 
+[[package]]
+name = "statistical"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49d57902bb128e5e38b5218d3681215ae3e322d99f65d5420e9849730d2ea372"
+dependencies = [
+ "num",
+ "rand 0.6.5",
+]
+
 [[package]]
 name = "string_cache"
 version = "0.8.7"
@@ -8867,6 +9460,16 @@ dependencies = [
  "crunchy",
 ]
 
+[[package]]
+name = "tinytemplate"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
 [[package]]
 name = "tinyvec"
 version = "1.8.0"
@@ -9064,7 +9667,7 @@ dependencies = [
  "indexmap 1.9.3",
  "pin-project",
  "pin-project-lite",
- "rand",
+ "rand 0.8.5",
  "slab",
  "tokio",
  "tokio-util",
@@ -9234,6 +9837,12 @@ version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
 
+[[package]]
+name = "ttf-parser"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4"
+
 [[package]]
 name = "tungstenite"
 version = "0.21.0"
@@ -9246,13 +9855,22 @@ dependencies = [
  "http 1.1.0",
  "httparse",
  "log",
- "rand",
+ "rand 0.8.5",
  "sha1",
  "thiserror 1.0.65",
  "url",
  "utf-8",
 ]
 
+[[package]]
+name = "twox-hash"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7b17f197b3050ba473acf9181f7b1d3b66d1cf7356c6cc57886662276e65908"
+dependencies = [
+ "rand 0.8.5",
+]
+
 [[package]]
 name = "typenum"
 version = "1.17.0"
@@ -9537,6 +10155,12 @@ dependencies = [
  "rustls-pki-types",
 ]
 
+[[package]]
+name = "weezl"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082"
+
 [[package]]
 name = "winapi"
 version = "0.3.9"
@@ -9784,6 +10408,15 @@ dependencies = [
  "windows-sys 0.48.0",
 ]
 
+[[package]]
+name = "wio"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5"
+dependencies = [
+ "winapi",
+]
+
 [[package]]
 name = "wyz"
 version = "0.5.1"
@@ -9814,6 +10447,17 @@ version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
 
+[[package]]
+name = "yeslogic-fontconfig-sys"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "503a066b4c037c440169d995b869046827dbc71263f6e8f3be6d77d4f3229dbd"
+dependencies = [
+ "dlib",
+ "once_cell",
+ "pkg-config",
+]
+
 [[package]]
 name = "zerocopy"
 version = "0.7.35"
diff --git a/Cargo.toml b/Cargo.toml
index 1aa14a013..575dcadce 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,6 +15,7 @@ members = [
   "crates/madara/client/block_import",
   "crates/madara/node",
   "crates/madara/primitives/block",
+  "crates/madara/primitives/bloom_filter",
   "crates/madara/primitives/convert",
   "crates/madara/primitives/transactions",
   "crates/madara/primitives/class",
@@ -46,6 +47,7 @@ default-members = [
   "crates/madara/client/analytics",
   "crates/madara/node",
   "crates/madara/primitives/block",
+  "crates/madara/primitives/bloom_filter",
   "crates/madara/primitives/convert",
   "crates/madara/primitives/transactions",
   "crates/madara/primitives/class",
@@ -110,6 +112,7 @@ m-proc-macros = { path = "crates/madara/proc-macros", default-features = false }
 
 # Madara primtitives
 mp-block = { path = "crates/madara/primitives/block", default-features = false }
+mp-bloom-filter = { path = "crates/madara/primitives/bloom_filter", default-features = false }
 mp-convert = { path = "crates/madara/primitives/convert", default-features = false }
 mp-transactions = { path = "crates/madara/primitives/transactions", default-features = false }
 mp-class = { path = "crates/madara/primitives/class", default-features = false }
@@ -167,6 +170,7 @@ alloy = { version = "0.4.0", features = [
 # Other third party dependencies
 paste = "1.0.15"
 anyhow = "1.0"
+ahash = "0.8"
 bigdecimal = "0.4.5"
 assert_matches = "1.5"
 async-trait = "0.1"
diff --git a/crates/madara/client/block_import/Cargo.toml b/crates/madara/client/block_import/Cargo.toml
index 13cf872d5..12ae9b897 100644
--- a/crates/madara/client/block_import/Cargo.toml
+++ b/crates/madara/client/block_import/Cargo.toml
@@ -32,6 +32,7 @@ tokio.workspace = true
 mc-analytics.workspace = true
 mc-db.workspace = true
 mp-block.workspace = true
+mp-bloom-filter.workspace = true
 mp-chain-config.workspace = true
 mp-class.workspace = true
 mp-convert.workspace = true
diff --git a/crates/madara/client/block_import/src/verify_apply.rs b/crates/madara/client/block_import/src/verify_apply.rs
index f830d23ab..89e29cf0b 100644
--- a/crates/madara/client/block_import/src/verify_apply.rs
+++ b/crates/madara/client/block_import/src/verify_apply.rs
@@ -9,6 +9,7 @@ use mp_block::{
     header::PendingHeader, BlockId, Header, MadaraBlockInfo, MadaraBlockInner, MadaraMaybePendingBlock,
     MadaraMaybePendingBlockInfo, MadaraPendingBlockInfo,
 };
+use mp_bloom_filter::EventBloomWriter;
 use mp_convert::{FeltHexDisplay, ToFelt};
 use starknet_api::core::ChainId;
 use starknet_types_core::felt::Felt;
@@ -82,6 +83,16 @@ pub fn verify_apply_inner(
     // Update contract and its storage tries
     let global_state_root = update_tries(backend, &block, &validation, block_number)?;
 
+    // Event bloom filter
+    let event_bloom = {
+        let mut events_iter = block.receipts.iter().flat_map(|tx| tx.events().iter()).peekable();
+        if events_iter.peek().is_none() {
+            None
+        } else {
+            Some(EventBloomWriter::from_events(events_iter))
+        }
+    };
+
     // Block hash
     let (block_hash, header) = block_hash(&block, &validation, block_number, parent_block_hash, global_state_root)?;
 
@@ -101,6 +112,7 @@ pub fn verify_apply_inner(
             },
             block.state_diff,
             block.converted_classes,
+            event_bloom,
             block.visited_segments,
             None,
         )
@@ -146,6 +158,7 @@ pub fn verify_apply_pending_inner(
             },
             block.state_diff,
             block.converted_classes,
+            None,
             block.visited_segments,
             None,
         )
@@ -411,7 +424,7 @@ mod verify_apply_tests {
         if populate_db {
             let header = create_dummy_header();
             let pending_block = finalized_block_zero(header);
-            backend.store_block(pending_block.clone(), finalized_state_diff_zero(), vec![], None, None).unwrap();
+            backend.store_block(pending_block.clone(), finalized_state_diff_zero(), vec![], None, None, None).unwrap();
         }
 
         // Create a validation context with the specified ignore_block_order flag
@@ -665,7 +678,7 @@ mod verify_apply_tests {
             let mut header = create_dummy_header();
             header.block_number = 0;
             let pending_block = finalized_block_zero(header);
-            backend.store_block(pending_block.clone(), finalized_state_diff_zero(), vec![], None, None).unwrap();
+            backend.store_block(pending_block.clone(), finalized_state_diff_zero(), vec![], None, None, None).unwrap();
 
             assert_eq!(backend.get_latest_block_n().unwrap(), Some(0));
 
@@ -691,7 +704,7 @@ mod verify_apply_tests {
             let mut header = create_dummy_header();
             header.block_number = 0;
             let pending_block = finalized_block_zero(header);
-            backend.store_block(pending_block.clone(), finalized_state_diff_zero(), vec![], None, None).unwrap();
+            backend.store_block(pending_block.clone(), finalized_state_diff_zero(), vec![], None, None, None).unwrap();
 
             assert_eq!(backend.get_latest_block_n().unwrap(), Some(0));
 
@@ -727,7 +740,7 @@ mod verify_apply_tests {
             let mut genesis_header = create_dummy_header();
             genesis_header.block_number = 0;
             let genesis_block = finalized_block_zero(genesis_header.clone());
-            backend.store_block(genesis_block, finalized_state_diff_zero(), vec![], None, None).unwrap();
+            backend.store_block(genesis_block, finalized_state_diff_zero(), vec![], None, None, None).unwrap();
 
             assert_eq!(backend.get_latest_block_n().unwrap(), Some(0));
 
@@ -773,7 +786,7 @@ mod verify_apply_tests {
             let mut genesis_header = create_dummy_header();
             genesis_header.block_number = 0;
             let genesis_block = finalized_block_zero(genesis_header.clone());
-            backend.store_block(genesis_block, finalized_state_diff_zero(), vec![], None, None).unwrap();
+            backend.store_block(genesis_block, finalized_state_diff_zero(), vec![], None, None, None).unwrap();
 
             assert_eq!(backend.get_latest_block_n().unwrap(), Some(0));
 
diff --git a/crates/madara/client/block_production/src/lib.rs b/crates/madara/client/block_production/src/lib.rs
index 131620c9b..d8866d6b6 100644
--- a/crates/madara/client/block_production/src/lib.rs
+++ b/crates/madara/client/block_production/src/lib.rs
@@ -451,6 +451,7 @@ impl<Mempool: MempoolProvider> BlockProductionTask<Mempool> {
             self.block.clone().into(),
             new_state_diff,
             self.declared_classes.clone(),
+            None,
             Some(visited_segments),
             Some(bouncer_weights),
         )?;
diff --git a/crates/madara/client/db/Cargo.toml b/crates/madara/client/db/Cargo.toml
index 0f9c30e2d..e93e0b0ae 100644
--- a/crates/madara/client/db/Cargo.toml
+++ b/crates/madara/client/db/Cargo.toml
@@ -21,6 +21,7 @@ targets = ["x86_64-unknown-linux-gnu"]
 # Madara
 mc-analytics = { workspace = true }
 mp-block = { workspace = true }
+mp-bloom-filter = { workspace = true }
 mp-chain-config = { workspace = true }
 mp-class = { workspace = true }
 mp-receipt = { workspace = true }
diff --git a/crates/madara/client/db/src/block_db.rs b/crates/madara/client/db/src/block_db.rs
index 17480ca96..cdd2d9ce4 100644
--- a/crates/madara/client/db/src/block_db.rs
+++ b/crates/madara/client/db/src/block_db.rs
@@ -8,8 +8,9 @@ use mp_block::{
     BlockId, BlockTag, MadaraBlock, MadaraBlockInfo, MadaraBlockInner, MadaraMaybePendingBlock,
     MadaraMaybePendingBlockInfo, MadaraPendingBlock, MadaraPendingBlockInfo, VisitedSegments,
 };
+use mp_bloom_filter::{EventBloomReader, EventBloomWriter};
 use mp_state_update::StateDiff;
-use rocksdb::WriteOptions;
+use rocksdb::{Direction, IteratorMode, WriteOptions};
 use starknet_api::core::ChainId;
 use starknet_types_core::felt::Felt;
 use starknet_types_rpc::EmittedEvent;
@@ -285,7 +286,12 @@ impl MadaraBackend {
 
     /// Also clears pending block
     #[tracing::instrument(skip(self), fields(module = "BlockDB"))]
-    pub(crate) fn block_db_store_block(&self, block: &MadaraBlock, state_diff: &StateDiff) -> Result<()> {
+    pub(crate) fn block_db_store_block(
+        &self,
+        block: &MadaraBlock,
+        state_diff: &StateDiff,
+        events_bloom: Option<EventBloomWriter>,
+    ) -> Result<()> {
         let mut tx = WriteBatchWithTransaction::default();
 
         let tx_hash_to_block_n = self.db.get_column(Column::TxHashToBlockN);
@@ -293,6 +299,7 @@ impl MadaraBackend {
         let block_n_to_block = self.db.get_column(Column::BlockNToBlockInfo);
         let block_n_to_block_inner = self.db.get_column(Column::BlockNToBlockInner);
         let block_n_to_state_diff = self.db.get_column(Column::BlockNToStateDiff);
+        let event_bloom = self.db.get_column(Column::EventBloom);
         let meta = self.db.get_column(Column::BlockStorageMeta);
 
         let block_hash_encoded = bincode::serialize(&block.info.block_hash)?;
@@ -306,6 +313,9 @@ impl MadaraBackend {
         tx.put_cf(&block_hash_to_block_n, block_hash_encoded, &block_n_encoded);
         tx.put_cf(&block_n_to_block_inner, &block_n_encoded, bincode::serialize(&block.inner)?);
         tx.put_cf(&block_n_to_state_diff, &block_n_encoded, bincode::serialize(state_diff)?);
+        if let Some(events_bloom) = events_bloom {
+            tx.put_cf(&event_bloom, &block_n_encoded, bincode::serialize(&events_bloom)?);
+        }
         tx.put_cf(&meta, ROW_SYNC_TIP, block_n_encoded);
 
         // susbcribers
@@ -480,4 +490,23 @@ impl MadaraBackend {
             }
         }
     }
+
+    #[tracing::instrument(skip(self), fields(module = "BlockDB"))]
+    pub fn get_event_filter_stream(
+        &self,
+        block_n: u64,
+    ) -> Result<impl Iterator<Item = Result<(u64, EventBloomReader)>> + '_> {
+        let col = self.db.get_column(Column::EventBloom);
+        let key = bincode::serialize(&block_n)?;
+        let iter_mode = IteratorMode::From(&key, Direction::Forward);
+        let iter = self.db.iterator_cf(&col, iter_mode);
+
+        Ok(iter.map(|kvs| {
+            kvs.map_err(MadaraStorageError::from).and_then(|(key, value)| {
+                let stored_block_n: u64 = bincode::deserialize(&key).map_err(MadaraStorageError::from)?;
+                let bloom = bincode::deserialize(&value).map_err(MadaraStorageError::from)?;
+                Ok((stored_block_n, bloom))
+            })
+        }))
+    }
 }
diff --git a/crates/madara/client/db/src/lib.rs b/crates/madara/client/db/src/lib.rs
index 33bc43c4e..9c773916d 100644
--- a/crates/madara/client/db/src/lib.rs
+++ b/crates/madara/client/db/src/lib.rs
@@ -108,6 +108,8 @@ pub enum Column {
     BlockHashToBlockN,
     /// One To One
     BlockNToStateDiff,
+    /// block_n => bloom filter for events
+    EventBloom,
     /// Meta column for block storage (sync tip, pending block)
     BlockStorageMeta,
 
@@ -178,6 +180,7 @@ impl Column {
             BlockHashToBlockN,
             BlockStorageMeta,
             BlockNToStateDiff,
+            EventBloom,
             ClassInfo,
             ClassCompiled,
             PendingClassInfo,
@@ -214,6 +217,7 @@ impl Column {
             BlockHashToBlockN => "block_hash_to_block_n",
             BlockStorageMeta => "block_storage_meta",
             BlockNToStateDiff => "block_n_to_state_diff",
+            EventBloom => "event_bloom",
             BonsaiContractsTrie => "bonsai_contracts_trie",
             BonsaiContractsFlat => "bonsai_contracts_flat",
             BonsaiContractsLog => "bonsai_contracts_log",
@@ -241,6 +245,12 @@ impl Column {
     }
 }
 
+#[cfg(test)]
+#[test]
+fn test_column_all() {
+    assert_eq!(Column::ALL.len(), Column::NUM_COLUMNS);
+}
+
 pub trait DatabaseExt {
     fn get_column(&self, col: Column) -> Arc<BoundColumnFamily<'_>>;
 }
diff --git a/crates/madara/client/db/src/storage_updates.rs b/crates/madara/client/db/src/storage_updates.rs
index b5f5ae59e..283be7347 100644
--- a/crates/madara/client/db/src/storage_updates.rs
+++ b/crates/madara/client/db/src/storage_updates.rs
@@ -4,6 +4,7 @@ use crate::MadaraStorageError;
 use blockifier::bouncer::BouncerWeights;
 use mp_block::VisitedSegments;
 use mp_block::{MadaraBlock, MadaraMaybePendingBlock, MadaraMaybePendingBlockInfo, MadaraPendingBlock};
+use mp_bloom_filter::EventBloomWriter;
 use mp_class::ConvertedClass;
 use mp_state_update::{
     ContractStorageDiffItem, DeployedContractItem, NonceUpdate, ReplacedClassItem, StateDiff, StorageEntry,
@@ -18,6 +19,7 @@ impl MadaraBackend {
         block: MadaraMaybePendingBlock,
         state_diff: StateDiff,
         converted_classes: Vec<ConvertedClass>,
+        events_bloom: Option<EventBloomWriter>,
         visited_segments: Option<VisitedSegments>,
         bouncer_weights: Option<BouncerWeights>,
     ) -> Result<(), MadaraStorageError> {
@@ -35,7 +37,7 @@ impl MadaraBackend {
                 bouncer_weights,
             ),
             MadaraMaybePendingBlockInfo::NotPending(info) => {
-                self.block_db_store_block(&MadaraBlock { info, inner: block.inner }, &state_diff_cpy)
+                self.block_db_store_block(&MadaraBlock { info, inner: block.inner }, &state_diff_cpy, events_bloom)
             }
         };
 
diff --git a/crates/madara/client/db/src/tests/test_block.rs b/crates/madara/client/db/src/tests/test_block.rs
index 2d7ed0c88..66f58d45f 100644
--- a/crates/madara/client/db/src/tests/test_block.rs
+++ b/crates/madara/client/db/src/tests/test_block.rs
@@ -24,8 +24,8 @@ mod block_tests {
         let block_hash = block.info.block_hash().unwrap();
         let state_diff = finalized_state_diff_zero();
 
-        backend.store_block(block.clone(), state_diff.clone(), vec![], None, None).unwrap();
-        backend.store_block(pending_block_one(), pending_state_diff_one(), vec![], None, None).unwrap();
+        backend.store_block(block.clone(), state_diff.clone(), vec![], None, None, None).unwrap();
+        backend.store_block(pending_block_one(), pending_state_diff_one(), vec![], None, None, None).unwrap();
 
         assert_eq!(backend.resolve_block_id(&BlockId::Hash(block_hash)).unwrap().unwrap(), DbBlockId::Number(0));
         assert_eq!(backend.resolve_block_id(&BlockId::Number(0)).unwrap().unwrap(), DbBlockId::Number(0));
@@ -52,7 +52,7 @@ mod block_tests {
         let block = finalized_block_zero(Header::default());
         let state_diff = finalized_state_diff_zero();
 
-        backend.store_block(block.clone(), state_diff.clone(), vec![], None, None).unwrap();
+        backend.store_block(block.clone(), state_diff.clone(), vec![], None, None, None).unwrap();
 
         assert_eq!(backend.get_block_hash(&BLOCK_ID_0).unwrap().unwrap(), block.info.block_hash().unwrap());
         assert_eq!(BLOCK_ID_0.resolve_db_block_id(backend).unwrap().unwrap(), BLOCK_ID_0);
@@ -75,7 +75,7 @@ mod block_tests {
         let block = pending_block_one();
         let state_diff = pending_state_diff_one();
 
-        backend.store_block(block.clone(), state_diff.clone(), vec![], None, None).unwrap();
+        backend.store_block(block.clone(), state_diff.clone(), vec![], None, None, None).unwrap();
 
         assert!(backend.get_block_hash(&BLOCK_ID_PENDING).unwrap().is_none());
         assert_eq!(backend.get_block_info(&BLOCK_ID_PENDING).unwrap().unwrap(), block.info);
@@ -92,9 +92,9 @@ mod block_tests {
         let backend = db.backend();
 
         backend
-            .store_block(finalized_block_zero(Header::default()), finalized_state_diff_zero(), vec![], None, None)
+            .store_block(finalized_block_zero(Header::default()), finalized_state_diff_zero(), vec![], None, None, None)
             .unwrap();
-        backend.store_block(pending_block_one(), pending_state_diff_one(), vec![], None, None).unwrap();
+        backend.store_block(pending_block_one(), pending_state_diff_one(), vec![], None, None, None).unwrap();
         backend.clear_pending_block().unwrap();
 
         assert!(backend.get_block(&BLOCK_ID_PENDING).unwrap().unwrap().inner.transactions.is_empty());
@@ -104,11 +104,11 @@ mod block_tests {
             "fake pending block parent hash must match with latest block in db"
         );
 
-        backend.store_block(finalized_block_one(), finalized_state_diff_one(), vec![], None, None).unwrap();
+        backend.store_block(finalized_block_one(), finalized_state_diff_one(), vec![], None, None, None).unwrap();
 
         let block_pending = pending_block_two();
         let state_diff = pending_state_diff_two();
-        backend.store_block(block_pending.clone(), state_diff.clone(), vec![], None, None).unwrap();
+        backend.store_block(block_pending.clone(), state_diff.clone(), vec![], None, None, None).unwrap();
 
         assert!(backend.get_block_hash(&BLOCK_ID_PENDING).unwrap().is_none());
         assert_eq!(backend.get_block_info(&BLOCK_ID_PENDING).unwrap().unwrap(), block_pending.info);
@@ -123,11 +123,11 @@ mod block_tests {
         let backend = db.backend();
 
         backend
-            .store_block(finalized_block_zero(Header::default()), finalized_state_diff_zero(), vec![], None, None)
+            .store_block(finalized_block_zero(Header::default()), finalized_state_diff_zero(), vec![], None, None, None)
             .unwrap();
 
         let latest_block = finalized_block_one();
-        backend.store_block(latest_block.clone(), finalized_state_diff_one(), vec![], None, None).unwrap();
+        backend.store_block(latest_block.clone(), finalized_state_diff_one(), vec![], None, None, None).unwrap();
 
         assert_eq!(backend.get_latest_block_n().unwrap().unwrap(), 1);
     }
@@ -152,7 +152,7 @@ mod block_tests {
         let block = finalized_block_zero(Header::default());
         let state_diff = finalized_state_diff_zero();
 
-        backend.store_block(block.clone(), state_diff.clone(), vec![], None, None).unwrap();
+        backend.store_block(block.clone(), state_diff.clone(), vec![], None, None, None).unwrap();
 
         let tx_hash_1 = block.info.tx_hashes()[1];
         assert_eq!(backend.find_tx_hash_block_info(&tx_hash_1).unwrap().unwrap(), (block.info.clone(), TxIndex(1)));
@@ -165,11 +165,11 @@ mod block_tests {
         let backend = db.backend();
 
         backend
-            .store_block(finalized_block_zero(Header::default()), finalized_state_diff_zero(), vec![], None, None)
+            .store_block(finalized_block_zero(Header::default()), finalized_state_diff_zero(), vec![], None, None, None)
             .unwrap();
 
         let block_pending = pending_block_one();
-        backend.store_block(block_pending.clone(), pending_state_diff_one(), vec![], None, None).unwrap();
+        backend.store_block(block_pending.clone(), pending_state_diff_one(), vec![], None, None, None).unwrap();
 
         let tx_hash_1 = block_pending.info.tx_hashes()[1];
         assert_eq!(
diff --git a/crates/madara/client/mempool/src/lib.rs b/crates/madara/client/mempool/src/lib.rs
index 91eb6fc27..e6c4fa082 100644
--- a/crates/madara/client/mempool/src/lib.rs
+++ b/crates/madara/client/mempool/src/lib.rs
@@ -1681,6 +1681,7 @@ mod test {
                 vec![],
                 None,
                 None,
+                None,
             )
             .expect("Failed to store block");
 
@@ -1722,6 +1723,7 @@ mod test {
                 vec![],
                 None,
                 None,
+                None,
             )
             .expect("Failed to store block");
 
diff --git a/crates/madara/client/rpc/Cargo.toml b/crates/madara/client/rpc/Cargo.toml
index 45f1e4280..d6101c5b9 100644
--- a/crates/madara/client/rpc/Cargo.toml
+++ b/crates/madara/client/rpc/Cargo.toml
@@ -29,6 +29,7 @@ mc-exec = { workspace = true }
 mc-gateway-client = { workspace = true }
 mc-mempool = { workspace = true }
 mp-block = { workspace = true, default-features = true }
+mp-bloom-filter = { workspace = true }
 mp-chain-config = { workspace = true }
 mp-class = { workspace = true }
 mp-convert = { workspace = true, default-features = true }
diff --git a/crates/madara/client/rpc/src/test_utils.rs b/crates/madara/client/rpc/src/test_utils.rs
index 79a786e08..132ec2ef4 100644
--- a/crates/madara/client/rpc/src/test_utils.rs
+++ b/crates/madara/client/rpc/src/test_utils.rs
@@ -245,6 +245,7 @@ pub fn make_sample_chain_for_block_getters(backend: &MadaraBackend) -> SampleCha
                 vec![],
                 None,
                 None,
+                None,
             )
             .unwrap();
 
@@ -270,6 +271,7 @@ pub fn make_sample_chain_for_block_getters(backend: &MadaraBackend) -> SampleCha
                 vec![],
                 None,
                 None,
+                None,
             )
             .unwrap();
 
@@ -339,6 +341,7 @@ pub fn make_sample_chain_for_block_getters(backend: &MadaraBackend) -> SampleCha
                 vec![],
                 None,
                 None,
+                None,
             )
             .unwrap();
 
@@ -377,6 +380,7 @@ pub fn make_sample_chain_for_block_getters(backend: &MadaraBackend) -> SampleCha
                 vec![],
                 None,
                 None,
+                None,
             )
             .unwrap();
     }
@@ -560,6 +564,7 @@ pub fn make_sample_chain_for_state_updates(backend: &MadaraBackend) -> SampleCha
                 vec![],
                 None,
                 None,
+                None,
             )
             .unwrap();
 
@@ -584,6 +589,7 @@ pub fn make_sample_chain_for_state_updates(backend: &MadaraBackend) -> SampleCha
                 vec![],
                 None,
                 None,
+                None,
             )
             .unwrap();
 
@@ -608,6 +614,7 @@ pub fn make_sample_chain_for_state_updates(backend: &MadaraBackend) -> SampleCha
                 vec![],
                 None,
                 None,
+                None,
             )
             .unwrap();
 
@@ -629,6 +636,7 @@ pub fn make_sample_chain_for_state_updates(backend: &MadaraBackend) -> SampleCha
                 vec![],
                 None,
                 None,
+                None,
             )
             .unwrap();
     }
diff --git a/crates/madara/client/rpc/src/utils/mod.rs b/crates/madara/client/rpc/src/utils/mod.rs
index 8878be332..97c8b387f 100644
--- a/crates/madara/client/rpc/src/utils/mod.rs
+++ b/crates/madara/client/rpc/src/utils/mod.rs
@@ -190,6 +190,11 @@ mod tests {
         vec![vec![Felt::from_hex_unchecked("0x1")], vec![Felt::from_hex_unchecked("0x2")]]
     }
 
+    #[fixture]
+    fn matching_keys_empty() -> Vec<Vec<Felt>> {
+        vec![vec![], vec![]]
+    }
+
     #[fixture]
     fn non_matching_keys() -> Vec<Vec<Felt>> {
         vec![vec![Felt::from_hex_unchecked("0x1")], vec![Felt::from_hex_unchecked("0x3")]]
@@ -200,6 +205,15 @@ mod tests {
         assert!(event_match_filter(&base_event, Some(&matching_address), Some(&matching_keys)));
     }
 
+    #[rstest]
+    fn test_address_and_empty_keys_match(
+        base_event: Event<Felt>,
+        matching_address: Felt,
+        matching_keys_empty: Vec<Vec<Felt>>,
+    ) {
+        assert!(event_match_filter(&base_event, Some(&matching_address), Some(&matching_keys_empty)));
+    }
+
     #[rstest]
     fn test_address_does_not_match(base_event: Event<Felt>, non_matching_address: Felt, matching_keys: Vec<Vec<Felt>>) {
         assert!(!event_match_filter(&base_event, Some(&non_matching_address), Some(&matching_keys)));
diff --git a/crates/madara/client/rpc/src/versions/user/v0_7_1/methods/read/block_hash_and_number.rs b/crates/madara/client/rpc/src/versions/user/v0_7_1/methods/read/block_hash_and_number.rs
index 43c173051..e275be5e4 100644
--- a/crates/madara/client/rpc/src/versions/user/v0_7_1/methods/read/block_hash_and_number.rs
+++ b/crates/madara/client/rpc/src/versions/user/v0_7_1/methods/read/block_hash_and_number.rs
@@ -55,6 +55,7 @@ mod tests {
                 vec![],
                 None,
                 None,
+                None,
             )
             .unwrap();
 
@@ -74,6 +75,7 @@ mod tests {
                 vec![],
                 None,
                 None,
+                None,
             )
             .unwrap();
 
@@ -96,6 +98,7 @@ mod tests {
                 vec![],
                 None,
                 None,
+                None,
             )
             .unwrap();
 
@@ -125,6 +128,7 @@ mod tests {
                 vec![],
                 None,
                 None,
+                None,
             )
             .unwrap();
 
diff --git a/crates/madara/client/rpc/src/versions/user/v0_7_1/methods/read/get_block_with_receipts.rs b/crates/madara/client/rpc/src/versions/user/v0_7_1/methods/read/get_block_with_receipts.rs
index 3438dca3a..d070f013b 100644
--- a/crates/madara/client/rpc/src/versions/user/v0_7_1/methods/read/get_block_with_receipts.rs
+++ b/crates/madara/client/rpc/src/versions/user/v0_7_1/methods/read/get_block_with_receipts.rs
@@ -253,6 +253,7 @@ mod tests {
                 vec![],
                 None,
                 None,
+                None,
             )
             .unwrap();
 
diff --git a/crates/madara/client/rpc/src/versions/user/v0_7_1/methods/read/get_events.rs b/crates/madara/client/rpc/src/versions/user/v0_7_1/methods/read/get_events.rs
index 30a560e6d..6310add5b 100644
--- a/crates/madara/client/rpc/src/versions/user/v0_7_1/methods/read/get_events.rs
+++ b/crates/madara/client/rpc/src/versions/user/v0_7_1/methods/read/get_events.rs
@@ -1,4 +1,5 @@
 use mp_block::{BlockId, BlockTag, MadaraMaybePendingBlock, MadaraMaybePendingBlockInfo};
+use mp_bloom_filter::EventBloomSearcher;
 use starknet_types_core::felt::Felt;
 use starknet_types_rpc::{EmittedEvent, Event, EventContent, EventFilterWithPageRequest, EventsChunk};
 
@@ -6,6 +7,7 @@ use crate::constants::{MAX_EVENTS_CHUNK_SIZE, MAX_EVENTS_KEYS};
 use crate::errors::{StarknetRpcApiError, StarknetRpcResult};
 use crate::types::ContinuationToken;
 use crate::utils::event_match_filter;
+use crate::utils::ResultExt;
 use crate::Starknet;
 
 /// Returns all events matching the given filter.
@@ -32,6 +34,8 @@ pub async fn get_events(
     starknet: &Starknet,
     filter: EventFilterWithPageRequest<Felt>,
 ) -> StarknetRpcResult<EventsChunk<Felt>> {
+    // TODO: use a continuation token like BlockN_EventN were EventN is the index of the event in the block and not the index of filtered events
+
     let from_address = filter.address;
     let keys = filter.keys;
     let chunk_size = filter.chunk_size;
@@ -61,13 +65,28 @@ pub async fn get_events(
     let from_block = continuation_token.block_n;
     let mut filtered_events: Vec<EmittedEvent<Felt>> = Vec::new();
 
-    for current_block in from_block..=to_block {
-        let (_pending, block) = if current_block <= latest_block {
-            (false, starknet.get_block(&BlockId::Number(current_block))?)
-        } else {
-            (true, starknet.get_block(&BlockId::Tag(BlockTag::Pending))?)
-        };
+    let iter_filter = starknet
+        .backend
+        .get_event_filter_stream(from_block)
+        .or_internal_server_error("Error getting event filter stream")?;
+
+    let key_filter = EventBloomSearcher::new(from_address.as_ref(), keys.as_deref());
+
+    for filter_block in iter_filter {
+        let (current_block, filter) = filter_block.or_internal_server_error("Error getting next filter block")?;
+
+        if current_block > latest_block {
+            break;
+        }
+
+        if !key_filter.search(&filter) {
+            continue;
+        }
+
+        let block =
+            starknet.get_block(&BlockId::Number(current_block)).or_internal_server_error("Error getting block")?;
 
+        // TODO: take only the events to fill the chunk not all the events
         let block_filtered_events: Vec<EmittedEvent<Felt>> = drain_block_events(block)
             .filter(|event| event_match_filter(&event.event, from_address.as_ref(), keys.as_deref()))
             .collect();
@@ -94,7 +113,12 @@ pub async fn get_events(
 
             return Ok(EventsChunk { events: filtered_events, continuation_token: token });
         }
+        if current_block == to_block {
+            break;
+        }
     }
+    // TODO: handle the case where 'to_block' is pending
+
     Ok(EventsChunk { events: filtered_events, continuation_token: None })
 }
 
diff --git a/crates/madara/client/rpc/src/versions/user/v0_8_0/methods/ws/subscribe_events.rs b/crates/madara/client/rpc/src/versions/user/v0_8_0/methods/ws/subscribe_events.rs
index 77c4897d9..2fb8f3b4f 100644
--- a/crates/madara/client/rpc/src/versions/user/v0_8_0/methods/ws/subscribe_events.rs
+++ b/crates/madara/client/rpc/src/versions/user/v0_8_0/methods/ws/subscribe_events.rs
@@ -151,6 +151,7 @@ mod test {
                     vec![],
                     None,
                     None,
+                    None,
                 )
                 .expect("Storing block");
 
diff --git a/crates/madara/client/rpc/src/versions/user/v0_8_0/methods/ws/subscribe_new_heads.rs b/crates/madara/client/rpc/src/versions/user/v0_8_0/methods/ws/subscribe_new_heads.rs
index 742981c66..05ea2b213 100644
--- a/crates/madara/client/rpc/src/versions/user/v0_8_0/methods/ws/subscribe_new_heads.rs
+++ b/crates/madara/client/rpc/src/versions/user/v0_8_0/methods/ws/subscribe_new_heads.rs
@@ -164,6 +164,7 @@ mod test {
                     vec![],
                     None,
                     None,
+                    None,
                 )
                 .expect("Storing block");
 
diff --git a/crates/madara/primitives/bloom_filter/Cargo.toml b/crates/madara/primitives/bloom_filter/Cargo.toml
new file mode 100644
index 000000000..10530e46a
--- /dev/null
+++ b/crates/madara/primitives/bloom_filter/Cargo.toml
@@ -0,0 +1,43 @@
+[package]
+description = "Madara primitive for block state update"
+name = "mp-bloom-filter"
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+version.workspace = true
+homepage.workspace = true
+
+[lints]
+workspace = true
+
+[package.metadata.docs.rs]
+targets = ["x86_64-unknown-linux-gnu"]
+
+[dependencies]
+ahash.workspace = true
+mp-receipt.workspace = true
+serde.workspace = true
+starknet-types-core.workspace = true
+thiserror.workspace = true
+
+[dev-dependencies]
+criterion = { version = "0.5", features = ["html_reports"] }
+twox-hash = "2.1"
+
+rand = "0.8"
+plotters = "0.3"
+statistical = "1.0"
+rayon.workspace = true
+bincode.workspace = true
+
+[profile.bench]
+opt-level = 3
+lto = "thin"
+codegen-units = 1
+debug = true
+
+[[bench]]
+name = "bloom_benchmark"
+harness = false
+path = "benches/bloom_benchmark.rs"
diff --git a/crates/madara/primitives/bloom_filter/benches/bloom_benchmark.rs b/crates/madara/primitives/bloom_filter/benches/bloom_benchmark.rs
new file mode 100644
index 000000000..100ea7584
--- /dev/null
+++ b/crates/madara/primitives/bloom_filter/benches/bloom_benchmark.rs
@@ -0,0 +1,192 @@
+use criterion::{black_box, criterion_group, criterion_main, Criterion};
+use rand::rngs::StdRng;
+use rand::{Rng, SeedableRng};
+use rayon::prelude::*;
+
+use mp_bloom_filter::{AtomicBitStore, BitStore, BloomFilter};
+
+use ahash::AHasher;
+use std::collections::hash_map::DefaultHasher;
+use std::hash::Hasher;
+use twox_hash::XxHash64;
+
+/// Configuration constants for benchmarks
+const KEY_SIZE: usize = 32;
+const HASH_COUNT: u8 = 7;
+const BITS_PER_ELEMENT: f64 = 9.6;
+const FALSE_POSITIF_RATE: f64 = 0.01;
+
+const TEST_DATA_SIZE: usize = 100_000;
+const LOOKUP_FILL_RATIO: f64 = 0.5;
+const SAMPLE_SIZE: usize = 50;
+const MEASUREMENT_TIME: u64 = 5;
+
+/// Type alias for test data
+type TestData = [u8; KEY_SIZE];
+
+type BenchFn = for<'a, 'b, 'c> fn(&'a mut Criterion, &'b [TestData], &'c str);
+
+struct HasherBenchmarks {
+    name: &'static str,
+    sequential_insertion: BenchFn,
+    parallel_insertion: BenchFn,
+    lookup: BenchFn,
+    parallel_lookup: BenchFn,
+}
+
+fn get_hasher_benchmarks<H: 'static + std::hash::Hasher + Default + Sync>(name: &'static str) -> HasherBenchmarks {
+    HasherBenchmarks {
+        name,
+        sequential_insertion: bench_sequential_insertion::<H> as BenchFn,
+        parallel_insertion: bench_parallel_insertion::<H> as BenchFn,
+        lookup: bench_lookup::<H> as BenchFn,
+        parallel_lookup: bench_parallel_lookup::<H> as BenchFn,
+    }
+}
+
+/// Generate test data for benchmarking
+fn generate_test_data(size: usize) -> Vec<TestData> {
+    let mut rng = StdRng::seed_from_u64(42);
+    (0..size)
+        .map(|_| {
+            let mut key = [0u8; KEY_SIZE];
+            rng.fill(&mut key);
+            key
+        })
+        .collect()
+}
+
+/// Create a pre-filled atomic filter for mutable lookups
+fn create_atomic_filter<H: std::hash::Hasher + Default>(test_data: &[TestData]) -> BloomFilter<H, AtomicBitStore> {
+    let size = (test_data.len() as f64 * BITS_PER_ELEMENT).ceil() as u64;
+    let filter = BloomFilter::<H, AtomicBitStore>::new(size, HASH_COUNT);
+
+    let items_to_insert = (test_data.len() as f64 * LOOKUP_FILL_RATIO) as usize;
+    for item in test_data.iter().take(items_to_insert) {
+        filter.add(item);
+    }
+    filter
+}
+
+/// Create a pre-filled readonly filter
+fn create_readonly_filter<H: std::hash::Hasher + Default>(test_data: &[TestData]) -> BloomFilter<H, BitStore> {
+    create_atomic_filter::<H>(test_data).finalize()
+}
+
+/// Benchmark sequential insertion
+fn bench_sequential_insertion<H: std::hash::Hasher + Default>(c: &mut Criterion, test_data: &[TestData], name: &str) {
+    let size = (test_data.len() as f64 * BITS_PER_ELEMENT).ceil() as u64;
+
+    let mut group = c.benchmark_group(format!("Sequential Insertion/{}", name));
+    group.sample_size(SAMPLE_SIZE);
+    group.measurement_time(std::time::Duration::from_secs(MEASUREMENT_TIME));
+
+    group.bench_function("atomic", |b| {
+        b.iter(|| {
+            let filter = BloomFilter::<H, AtomicBitStore>::new(size, HASH_COUNT);
+            for item in test_data {
+                filter.add(black_box(item));
+            }
+        });
+    });
+
+    // group.bench_function("non atomic", |b| {
+    //     b.iter(|| {
+    //         let mut filter = BloomFilter::<H, BitStore>::optimal(TEST_DATA_SIZE, FALSE_POSITIVE_RATE)
+    //             .expect("Failed to create optimal filter");
+    //         for item in test_data {
+    //             filter.add(black_box(item));
+    //         }
+    //     });
+    // });
+
+    group.finish();
+}
+
+/// Benchmark parallel insertion using rayon
+fn bench_parallel_insertion<H: Hasher + Default + Sync>(c: &mut Criterion, test_data: &[TestData], name: &str) {
+    let size = (test_data.len() as f64 * BITS_PER_ELEMENT).ceil() as u64;
+
+    let mut group = c.benchmark_group(format!("Parallel Insertion/{}", name));
+    group.sample_size(SAMPLE_SIZE);
+    group.measurement_time(std::time::Duration::from_secs(MEASUREMENT_TIME));
+
+    group.bench_function("atomic", |b| {
+        b.iter(|| {
+            let filter = BloomFilter::<H, AtomicBitStore>::new(size, HASH_COUNT);
+            test_data.par_iter().for_each(|item| {
+                filter.add(black_box(item));
+            });
+        });
+    });
+
+    group.finish();
+}
+
+/// Benchmark lookups
+fn bench_lookup<H: std::hash::Hasher + Default + Sync>(c: &mut Criterion, test_data: &[TestData], name: &str) {
+    let mut group = c.benchmark_group(format!("Lookup/{}", name));
+    group.sample_size(SAMPLE_SIZE);
+    group.measurement_time(std::time::Duration::from_secs(MEASUREMENT_TIME));
+
+    let readonly_filter = create_readonly_filter::<H>(test_data);
+
+    // Benchmark readonly filter lookups
+    group.bench_function("non atomic", |b| {
+        b.iter(|| {
+            for item in test_data {
+                black_box(readonly_filter.might_contain(item));
+            }
+        });
+    });
+
+    group.finish();
+}
+
+/// Benchmark parallel lookups using rayon
+fn bench_parallel_lookup<H: std::hash::Hasher + Default + Sync>(c: &mut Criterion, test_data: &[TestData], name: &str) {
+    let mut group = c.benchmark_group(format!("Parallel Lookup/{}", name));
+    group.sample_size(SAMPLE_SIZE);
+    group.measurement_time(std::time::Duration::from_secs(MEASUREMENT_TIME));
+
+    let readonly_filter = create_readonly_filter::<H>(test_data);
+
+    // Benchmark parallel readonly filter lookups
+    group.bench_function("non atomic", |b| {
+        b.iter(|| {
+            test_data.par_iter().for_each(|item| {
+                black_box(readonly_filter.might_contain(item));
+            });
+        });
+    });
+
+    group.finish();
+}
+
+fn bench_bloom_filters(c: &mut Criterion) {
+    let test_data = generate_test_data(TEST_DATA_SIZE);
+
+    // Test different hash functions
+    let hashers = [
+        get_hasher_benchmarks::<DefaultHasher>("DefaultHasher"),
+        get_hasher_benchmarks::<AHasher>("AHasher"),
+        get_hasher_benchmarks::<XxHash64>("XxHash64"),
+    ];
+
+    for hasher in &hashers {
+        (hasher.sequential_insertion)(c, &test_data, hasher.name);
+        (hasher.parallel_insertion)(c, &test_data, hasher.name);
+        (hasher.lookup)(c, &test_data, hasher.name);
+        (hasher.parallel_lookup)(c, &test_data, hasher.name);
+    }
+}
+
+criterion_group! {
+    name = benches;
+    config = Criterion::default()
+        .with_plots()
+        .sample_size(SAMPLE_SIZE);
+    targets = bench_bloom_filters
+}
+
+criterion_main!(benches);
diff --git a/crates/madara/primitives/bloom_filter/src/constants.rs b/crates/madara/primitives/bloom_filter/src/constants.rs
new file mode 100644
index 000000000..56d2be1d5
--- /dev/null
+++ b/crates/madara/primitives/bloom_filter/src/constants.rs
@@ -0,0 +1,6 @@
+pub const MIN_FILTER_SIZE: usize = 64;
+pub const MAX_FILTER_SIZE: usize = 1 << 24; // 16 MiB
+pub const MIN_HASH_COUNT: usize = 1;
+pub const MAX_HASH_COUNT: usize = 20;
+pub const DEFAULT_SIZE: usize = 1024;
+pub const DEFAULT_HASH_COUNT: usize = 3;
diff --git a/crates/madara/primitives/bloom_filter/src/error.rs b/crates/madara/primitives/bloom_filter/src/error.rs
new file mode 100644
index 000000000..5cb12b38b
--- /dev/null
+++ b/crates/madara/primitives/bloom_filter/src/error.rs
@@ -0,0 +1,11 @@
+#[derive(Debug, thiserror::Error)]
+pub enum BloomError {
+    #[error("Filter size {0} is too small (minimum: {1})")]
+    SizeTooSmall(usize, usize),
+    #[error("Filter size {0} is too large (maximum: {1})")]
+    SizeTooLarge(usize, usize),
+    #[error("Hash count {0} is too small (minimum: {1})")]
+    TooFewHashes(usize, usize),
+    #[error("Hash count {0} is too large (maximum: {1})")]
+    TooManyHashes(usize, usize),
+}
diff --git a/crates/madara/primitives/bloom_filter/src/events.rs b/crates/madara/primitives/bloom_filter/src/events.rs
new file mode 100644
index 000000000..63b6927f5
--- /dev/null
+++ b/crates/madara/primitives/bloom_filter/src/events.rs
@@ -0,0 +1,143 @@
+use ahash::AHasher;
+use mp_receipt::Event;
+use serde::{Deserialize, Deserializer, Serialize};
+use starknet_types_core::felt::Felt;
+use std::{collections::HashSet, fmt};
+
+use crate::{AtomicBitStore, BitStore, BloomFilter, PreCalculatedHashes};
+
+/// Number of hash functions used in the Bloom filter.
+/// The value 7 is optimal for a false positive rate of 1% (0.01).
+/// Formula used: k = -ln(p)/ln(2) ≈ 7
+/// where:
+/// - k is the number of hash functions
+/// - p is the desired false positive rate (0.01)
+const HASH_COUNT: u8 = 7;
+
+/// Number of bits per element in the Bloom filter.
+/// The value 9.6 is derived from the formula for optimal size:
+/// m/n = -ln(p)/ln(2)² ≈ 9.6
+/// where:
+/// - m is the size of the filter in bits
+/// - n is the number of elements
+/// - p is the desired false positive rate (0.01)
+const BITS_PER_ELEMENT: f64 = 9.6;
+
+type BloomHasher = AHasher;
+
+pub struct EventBloomWriter {
+    filter: BloomFilter<BloomHasher, AtomicBitStore>,
+}
+
+impl fmt::Debug for EventBloomWriter {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "EventBloomWriter {{ size: {}, hash_count: {} }}", self.size(), HASH_COUNT)
+    }
+}
+
+impl Serialize for EventBloomWriter {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        self.filter.storage().serialize(serializer)
+    }
+}
+
+impl EventBloomWriter {
+    pub fn from_events<'a, I>(events: I) -> Self
+    where
+        I: Iterator<Item = &'a Event> + 'a,
+    {
+        let mut unique_keys = HashSet::new();
+
+        for key in Self::events_to_bloom_keys(events) {
+            unique_keys.insert(key);
+        }
+
+        let size = (unique_keys.len() as f64 * BITS_PER_ELEMENT).ceil() as _;
+
+        let filter = BloomFilter::<_, AtomicBitStore>::new(size, HASH_COUNT);
+
+        for key in unique_keys {
+            filter.add(&key);
+        }
+        Self { filter }
+    }
+
+    pub fn size(&self) -> u64 {
+        self.filter.len()
+    }
+
+    fn events_to_bloom_keys<'a, I>(events: I) -> impl Iterator<Item = [u8; 33]> + 'a
+    where
+        I: Iterator<Item = &'a Event> + 'a,
+    {
+        events.flat_map(Self::event_to_bloom_keys)
+    }
+
+    fn event_to_bloom_keys(event: &Event) -> impl Iterator<Item = [u8; 33]> + '_ {
+        let from_address_key = create_bloom_key(0, &event.from_address);
+
+        let keys = event.keys.iter().enumerate().map(|(i, key)| create_bloom_key(i as u8 + 1, key));
+
+        std::iter::once(from_address_key).chain(keys)
+    }
+}
+
+pub struct EventBloomReader {
+    filter: BloomFilter<BloomHasher, BitStore>,
+}
+
+impl<'de> Deserialize<'de> for EventBloomReader {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let storage = BitStore::deserialize(deserializer)?;
+
+        Ok(Self { filter: BloomFilter::from_storage(storage, HASH_COUNT) })
+    }
+}
+
+pub struct EventBloomSearcher {
+    patterns: Vec<Vec<PreCalculatedHashes>>,
+}
+
+impl EventBloomSearcher {
+    pub fn new(from_address: Option<&Felt>, keys: Option<&[Vec<Felt>]>) -> Self {
+        let mut patterns = Vec::new();
+
+        if let Some(from_address) = from_address {
+            let from_address_key = create_bloom_key(0, from_address);
+            patterns.push(vec![PreCalculatedHashes::new::<BloomHasher, _>(HASH_COUNT, &from_address_key)]);
+        }
+
+        if let Some(keys) = keys {
+            for (i, key) in keys.iter().enumerate() {
+                if key.is_empty() {
+                    continue;
+                }
+                patterns.push(
+                    key.iter()
+                        .map(|key| create_bloom_key(i as u8 + 1, key))
+                        .map(|key| PreCalculatedHashes::new::<BloomHasher, _>(HASH_COUNT, &key))
+                        .collect(),
+                );
+            }
+        }
+
+        Self { patterns }
+    }
+
+    pub fn search(&self, filter: &EventBloomReader) -> bool {
+        self.patterns.iter().all(|pattern| pattern.iter().any(|hashes| filter.filter.might_contain_hashes(hashes)))
+    }
+}
+
+fn create_bloom_key(index: u8, key: &Felt) -> [u8; 33] {
+    let mut bloom_key = [0u8; 33];
+    bloom_key[0] = index;
+    bloom_key[1..].copy_from_slice(&key.to_bytes_be());
+    bloom_key
+}
diff --git a/crates/madara/primitives/bloom_filter/src/filter.rs b/crates/madara/primitives/bloom_filter/src/filter.rs
new file mode 100644
index 000000000..1eba2d9f8
--- /dev/null
+++ b/crates/madara/primitives/bloom_filter/src/filter.rs
@@ -0,0 +1,272 @@
+//! Bloom filter implementation with configurable hash functions and storage backends.
+//!
+//! A Bloom filter is a space-efficient probabilistic data structure used to test whether an element
+//! is a member of a set. False positive matches are possible, but false negatives are not. In other
+//! words, a query returns either "possibly in set" or "definitely not in set."
+//!
+//! # Features
+//!
+//! - Generic over hash functions
+//! - Supports both thread-safe (atomic) and single-threaded storage backends
+//! - Pre-calculated hash optimization for repeated queries
+//! - Serialization support via serde
+
+use crate::storage::{AtomicBitStore, BitStore};
+use serde::{Deserialize, Serialize};
+use std::hash::{Hash, Hasher};
+use std::marker::PhantomData;
+/// A cache of computed hash positions that can be reused across different filter sizes.
+///
+/// This structure stores the raw hash values generated for an item, allowing them to be
+/// efficiently mapped to different filter sizes without recalculating the hash functions.
+/// This is particularly useful when the same item needs to be checked against multiple
+/// Bloom filters of different sizes.
+///
+/// # Requirements
+///
+/// The number of hash functions used to create this pre-calculated hash set must match
+/// the number of hash functions used by any Bloom filter it's used with. Attempting to
+/// use these hashes with a filter having a different hash count will result in incorrect
+/// behavior.
+#[derive(Debug, Clone)]
+pub struct PreCalculatedHashes {
+    /// Raw hash values before modulo operation
+    raw_hashes: Vec<u64>,
+    /// Number of hash functions used
+    hash_count: u8,
+}
+
+impl PreCalculatedHashes {
+    /// Creates a new instance with pre-calculated hash values for the given item.
+    ///
+    /// # Type Parameters
+    ///
+    /// * `H`: The hasher implementation to use (must implement `Hasher + Default`)
+    /// * `T`: The type of item being hashed (must implement `Hash`)
+    ///
+    /// # Parameters
+    ///
+    /// * `hash_count`: Number of hash functions to use (must match the target filter's hash count)
+    /// * `item`: Reference to the item to generate hashes for
+    ///
+    /// # Returns
+    ///
+    /// A new `PreCalculatedHashes` instance containing the computed hash values
+    pub fn new<H: Hasher + Default, T: Hash>(hash_count: u8, item: &T) -> Self {
+        Self { raw_hashes: calculate_hashes::<H, T>(hash_count, item).collect(), hash_count }
+    }
+
+    /// Returns the number of hash functions used in these pre-calculations.
+    ///
+    /// This value represents how many different bit positions will be generated
+    /// for each filter size and must match the target filter's hash count.
+    #[inline]
+    pub fn hash_count(&self) -> u8 {
+        self.hash_count
+    }
+
+    /// Returns a reference to the raw hash values before modulo operation.
+    ///
+    /// These values are the unmodified results from the hash functions,
+    /// before being mapped to specific filter sizes.
+    #[inline]
+    pub fn raw_hashes(&self) -> &[u64] {
+        &self.raw_hashes
+    }
+
+    /// Calculates bit positions for a specific filter size using the pre-calculated hashes.
+    ///
+    /// This method maps the raw hash values to actual bit positions within a filter
+    /// of the specified size using modulo operation.
+    ///
+    /// # Parameters
+    ///
+    /// * `filter_size`: The size of the target Bloom filter in bits
+    ///
+    /// # Returns
+    ///
+    /// An iterator yielding `hash_count` bit positions, each in the range [0, filter_size)
+    ///
+    /// # Notes
+    ///
+    /// - The returned positions are guaranteed to be within the valid range for the
+    ///   specified filter size, but may not be unique if collisions occur.
+    /// - The target Bloom filter must have the same number of hash functions as was used
+    ///   to create these pre-calculated hashes. Using these positions with a filter that
+    ///   has a different hash count will lead to incorrect behavior.
+    #[inline]
+    pub fn get_positions_for_size(&self, filter_size: u64) -> impl Iterator<Item = usize> + '_ {
+        self.raw_hashes.iter().map(move |hash| (hash % filter_size) as usize)
+    }
+}
+
+/// A Bloom filter implementation with configurable hash function and storage backend.
+///
+/// # Type Parameters
+///
+/// * `H`: The hasher implementation to use for generating hash values
+/// * `B`: The storage backend type (either `AtomicBitStore` for mutable or `BitStore` for immutable)
+///
+/// # Fields
+///
+/// * `storage`: The bit array storage backend
+/// * `bit_size`: The total number of bits in the filter
+/// * `hash_count`: The number of hash functions used
+/// * `_hasher`: Phantom data for the hasher type
+#[derive(Serialize, Deserialize, Clone)]
+pub struct BloomFilter<H: Hasher + Default, B> {
+    storage: B,
+    bit_size: u64,
+    hash_count: u8,
+    #[serde(skip)]
+    _hasher: PhantomData<H>,
+}
+
+impl<H: Hasher + Default> BloomFilter<H, AtomicBitStore> {
+    /// Creates a new Bloom filter with the specified size and number of hash functions.
+    ///
+    /// The actual size will be aligned to the next multiple of 64 bits for efficient storage.
+    ///
+    /// # Arguments
+    ///
+    /// * `size`: The desired size of the filter in bits
+    /// * `hash_count`: The number of hash functions to use
+    pub fn new(size: u64, hash_count: u8) -> Self {
+        let size_in_u64s = (size.max(1) + 63) >> 6;
+        let aligned_size = size_in_u64s << 6;
+
+        Self {
+            storage: AtomicBitStore::new(size_in_u64s as _),
+            bit_size: aligned_size,
+            hash_count,
+            _hasher: PhantomData,
+        }
+    }
+
+    /// Converts this mutable Bloom filter into an immutable one.
+    ///
+    /// This is useful when you're done adding elements and want to switch to a read-only version.
+    pub fn finalize(self) -> BloomFilter<H, BitStore> {
+        BloomFilter {
+            storage: self.storage.to_readonly(),
+            bit_size: self.bit_size,
+            hash_count: self.hash_count,
+            _hasher: PhantomData,
+        }
+    }
+
+    /// Adds an item to the Bloom filter.
+    ///
+    /// # Arguments
+    ///
+    /// * `item`: The item to add, which must implement the Hash trait
+    pub fn add<T: Hash>(&self, item: &T) {
+        calculate_hashes::<H, T>(self.hash_count, item)
+            .for_each(|hash| self.storage.set_bit((hash % self.bit_size) as usize));
+    }
+}
+
+impl<H: Hasher + Default, B> BloomFilter<H, B> {
+    /// Returns the size of the Bloom filter in bits.
+    pub fn len(&self) -> u64 {
+        self.bit_size
+    }
+
+    /// Returns the number of hash functions used by the filter.
+    pub fn hash_count(&self) -> u8 {
+        self.hash_count
+    }
+
+    pub(crate) fn storage(&self) -> &B {
+        &self.storage
+    }
+}
+
+impl<H: Hasher + Default> BloomFilter<H, BitStore> {
+    /// Creates a new immutable Bloom filter from a bit storage backend.
+    pub fn from_storage(storage: BitStore, hash_count: u8) -> Self {
+        let bit_size = storage.len() as _;
+        Self { storage, bit_size, hash_count, _hasher: PhantomData }
+    }
+
+    /// Tests whether an item might be in the set (immutable version).
+    pub fn might_contain<T: Hash>(&self, item: &T) -> bool {
+        calculate_hashes::<H, T>(self.hash_count, item)
+            .all(|hash| self.storage.test_bit((hash % self.bit_size) as usize))
+    }
+
+    /// Tests whether an item might be in the set using pre-calculated hash values.
+    pub fn might_contain_hashes(&self, pre_calculated: &PreCalculatedHashes) -> bool {
+        pre_calculated.get_positions_for_size(self.bit_size).all(|pos| self.storage.test_bit(pos))
+    }
+
+    /// Estimates the current false positive rate of the filter based on
+    /// the number of bits set and the number of hash functions.
+    pub fn estimated_false_positive_rate(&self) -> f64 {
+        let set_bits = self.storage.count_ones() as f64;
+        let total_bits = self.storage.len() as f64;
+        (set_bits / total_bits).powi(self.hash_count as i32)
+    }
+}
+
+/// Calculates multiple hash positions for an item using double hashing technique.
+///
+/// This function implements a double hashing strategy to generate k different bit
+/// positions using the formula:
+///
+/// h(i) = (h1 + i * h2) mod m
+///
+/// where:
+/// - h1 is the primary hash value of the item
+/// - h2 is a secondary hash derived from h1 using rotation and golden ratio multiplication
+/// - m is the bit array size (applied implicitly by the caller)
+/// - i iterates from 0 to k-1, with k being the number of desired hash functions
+///
+/// # Mathematical Properties
+///
+/// ## Golden Ratio Constant (11400714819323198485)
+/// The function uses PHI = 2^64/φ where φ is the golden ratio. This value is chosen because:
+/// - It provides optimal distribution properties for hashing
+/// - When used in multiplication, it ensures good bit avalanche effects
+/// - Its binary representation has excellent bit distribution
+/// - It minimizes correlation between input and output bits
+///
+/// ## Bit Rotation (17)
+/// The 17-bit rotation was selected because:
+/// - It is coprime with 64 (word size), ensuring maximum period length
+/// - Provides effective bit mixing while remaining computationally efficient
+/// - As an odd number, it avoids potential patterns from even rotations
+/// - Creates balanced mixing between upper and lower word bits
+///
+/// The combination of golden ratio multiplication and 17-bit rotation ensures both
+/// strong avalanche properties and computational efficiency.
+///
+/// # Parameters
+///
+/// * `hash_count`: Number of hash positions to generate
+/// * `item`: Reference to the item to be hashed
+///
+/// # Type Parameters
+///
+/// * `H`: A hasher type that implements `Hasher` + `Default`
+/// * `T`: The type of item being hashed, must implement `Hash`
+///
+/// # Returns
+///
+/// Returns an iterator yielding `hash_count` different hash values. Each value
+/// represents a position that can be mapped to the underlying bit array through
+/// modulo operation by the caller.
+///
+/// Note: The actual bit positions should be computed by the caller by applying
+/// modulo with the bit array size to the returned hash values.
+#[inline]
+fn calculate_hashes<H: Hasher + Default, T: Hash>(hash_count: u8, item: &T) -> impl Iterator<Item = u64> + '_ {
+    let mut hasher1 = H::default();
+    item.hash(&mut hasher1);
+    let h1 = hasher1.finish();
+
+    const PHI: u64 = 0x9e37_79b9_7f4a_7c15; // 2^64/φ
+    let h2 = h1.rotate_left(17).wrapping_mul(PHI);
+
+    (0..hash_count).map(move |i| h1.wrapping_add(h2.wrapping_mul(i as u64)))
+}
diff --git a/crates/madara/primitives/bloom_filter/src/lib.rs b/crates/madara/primitives/bloom_filter/src/lib.rs
new file mode 100644
index 000000000..001d4a885
--- /dev/null
+++ b/crates/madara/primitives/bloom_filter/src/lib.rs
@@ -0,0 +1,17 @@
+// mod config;
+mod constants;
+mod error;
+mod events;
+mod filter;
+mod storage;
+
+// pub use config::BloomConfig;
+pub use constants::*;
+pub use error::BloomError;
+pub use filter::{BloomFilter, PreCalculatedHashes};
+pub use storage::{AtomicBitStore, BitStore};
+
+pub use events::{EventBloomReader, EventBloomSearcher, EventBloomWriter};
+
+#[cfg(test)]
+mod tests;
diff --git a/crates/madara/primitives/bloom_filter/src/storage.rs b/crates/madara/primitives/bloom_filter/src/storage.rs
new file mode 100644
index 000000000..5d118b547
--- /dev/null
+++ b/crates/madara/primitives/bloom_filter/src/storage.rs
@@ -0,0 +1,204 @@
+//! Storage backends for Bloom filters.
+//!
+//! This module provides both atomic (thread-safe) and non-atomic storage implementations
+//! for the bit array used by the Bloom filter.
+
+use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
+use std::{
+    fmt,
+    sync::atomic::{AtomicU64, Ordering},
+};
+
+/// An immutable bit storage implementation using regular u64 values.
+#[derive(Serialize, Deserialize, Clone)]
+#[repr(transparent)]
+pub struct BitStore {
+    bits: Box<[u64]>,
+}
+
+/// A thread-safe bit storage implementation using atomic u64 values.
+#[repr(transparent)]
+pub struct AtomicBitStore {
+    bits: Box<[AtomicU64]>,
+}
+
+#[cfg(test)]
+impl Clone for AtomicBitStore {
+    fn clone(&self) -> Self {
+        let cloned_bits = self
+            .bits
+            .iter()
+            .map(|atomic| {
+                let value = atomic.load(std::sync::atomic::Ordering::Relaxed);
+                std::sync::atomic::AtomicU64::new(value)
+            })
+            .collect::<Vec<_>>()
+            .into_boxed_slice();
+
+        Self { bits: cloned_bits }
+    }
+}
+
+/// Custom serialization for AtomicBitStore that converts atomic values to regular u64s.
+impl Serialize for AtomicBitStore {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let regular_bits: Vec<u64> = self.bits.iter().map(|atomic| atomic.load(Ordering::Relaxed)).collect();
+        regular_bits.serialize(serializer)
+    }
+}
+
+/// Custom visitor for deserializing AtomicBitStore from regular u64 values.
+struct AtomicBitStoreVisitor;
+
+impl<'de> Visitor<'de> for AtomicBitStoreVisitor {
+    type Value = AtomicBitStore;
+
+    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
+        formatter.write_str("an array of unsigned 64-bit integers")
+    }
+
+    fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
+    where
+        A: serde::de::SeqAccess<'de>,
+    {
+        let mut regular_bits = Vec::new();
+        while let Some(value) = seq.next_element()? {
+            regular_bits.push(value);
+        }
+
+        let atomic_bits = regular_bits.into_iter().map(AtomicU64::new).collect();
+
+        Ok(AtomicBitStore { bits: atomic_bits })
+    }
+}
+
+impl<'de> Deserialize<'de> for AtomicBitStore {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        deserializer.deserialize_seq(AtomicBitStoreVisitor)
+    }
+}
+
+impl BitStore {
+    /// Creates a new BitStore from a boxed slice of u64 values.
+    #[inline]
+    pub(crate) fn new_from_slice(bits: Box<[u64]>) -> Self {
+        Self { bits }
+    }
+
+    /// Returns the total number of bits in the store.
+    #[inline]
+    pub(crate) fn len(&self) -> usize {
+        self.bits.len() << 6
+    }
+
+    /// Counts the total number of set bits (ones) in the store.
+    #[inline]
+    pub(crate) fn count_ones(&self) -> usize {
+        self.bits.iter().map(|&block| block.count_ones() as usize).sum()
+    }
+
+    /// Tests whether a specific bit is set.
+    ///
+    /// # Arguments
+    ///
+    /// * `index`: The bit position to test
+    ///
+    /// # Implementation Notes
+    ///
+    /// The function uses direct indexing since input validation is performed at the hash
+    /// generation level, making additional bounds checking redundant.
+    #[inline]
+    pub(crate) fn test_bit(&self, index: usize) -> bool {
+        let (block_index, bit_mask) = bit_position(index);
+        self.bits[block_index] & bit_mask != 0
+    }
+
+    #[cfg(test)]
+    pub(crate) fn fingerprint(&self) -> u64 {
+        use std::hash::{Hash, Hasher};
+
+        let mut hasher = std::collections::hash_map::DefaultHasher::default();
+        for b in &self.bits {
+            b.hash(&mut hasher);
+        }
+        hasher.finish()
+    }
+}
+
+impl AtomicBitStore {
+    /// Creates a new AtomicBitStore with the specified size in u64 blocks.
+    pub(crate) fn new(size_in_u64s: usize) -> Self {
+        let bits = (0..size_in_u64s).map(|_| AtomicU64::new(0)).collect();
+        Self { bits }
+    }
+
+    /// Sets a specific bit to 1.
+    ///
+    /// # Arguments
+    ///
+    /// * `index`: The bit position to set
+    ///
+    /// # Implementation Notes
+    ///
+    /// The function uses direct indexing since input validation is performed at the hash
+    /// generation level, making additional bounds checking redundant.
+    #[inline]
+    pub(crate) fn set_bit(&self, index: usize) {
+        let (block_index, bit_mask) = bit_position(index);
+        self.bits[block_index].fetch_or(bit_mask, Ordering::Relaxed);
+    }
+
+    /// Converts this atomic store into a regular BitStore.
+    pub(crate) fn to_readonly(self) -> BitStore {
+        let bits = self.bits.iter().map(|b| b.load(Ordering::Relaxed)).collect();
+        BitStore::new_from_slice(bits)
+    }
+
+    #[cfg(test)]
+    pub(crate) fn fingerprint(&self) -> u64 {
+        use std::hash::{Hash, Hasher};
+
+        let mut hasher = std::collections::hash_map::DefaultHasher::default();
+        for b in &self.bits {
+            b.load(Ordering::Relaxed).hash(&mut hasher);
+        }
+        hasher.finish()
+    }
+}
+
+/// Calculates the block index and bit mask for a given bit position.
+///
+/// The bit position is split into two components:
+/// - A block index identifying which u64 block contains the bit
+/// - A bit mask with a single 1 at the target position within that block
+///
+/// # Arguments
+///
+/// * `index`: Global bit position in the bitset
+///
+/// # Returns
+///
+/// A tuple containing:
+/// * `block_index`: Index of the u64 block (index >> 6 is equivalent to index / 64)
+/// * `bit_mask`: A u64 with a single bit set (1 << position_in_block)
+///
+/// # Implementation Notes
+///
+/// - Uses bit shifts for efficient division/modulo:
+///   - `>> 6` divides by 64 to get block index
+///   - `& 0x3F` is equivalent to modulo 64 for bit position
+/// - Operations are const and will be computed at compile time when possible
+#[inline(always)]
+const fn bit_position(index: usize) -> (usize, u64) {
+    // Calculate which u64 block contains our bit
+    let block_index = index >> 6;
+    // Create a mask with a 1 bit in the correct position (0-63) within the block
+    let bit_mask = 1u64 << (index & 0x3F);
+    (block_index, bit_mask)
+}
diff --git a/crates/madara/primitives/bloom_filter/src/tests/filter_tests.rs b/crates/madara/primitives/bloom_filter/src/tests/filter_tests.rs
new file mode 100644
index 000000000..2a69ff338
--- /dev/null
+++ b/crates/madara/primitives/bloom_filter/src/tests/filter_tests.rs
@@ -0,0 +1,191 @@
+use crate::*;
+use rand::{thread_rng, Rng};
+use rayon::prelude::*;
+use std::collections::{hash_map::DefaultHasher, HashSet};
+use tests::utils::{create_filter, FALSE_POSITIF_RATE, HASH_COUNT};
+
+#[test]
+fn test_basic_operations() {
+    const NB_ELEM: u64 = 2;
+
+    let filter = create_filter::<DefaultHasher>(NB_ELEM);
+    let key1 = b"Hello";
+    let key2 = b"World";
+
+    // Test adding elements
+    filter.add(key1);
+    filter.add(key2);
+
+    // Test conversion to read-only and lookups
+    let ro_filter = filter.finalize();
+    assert!(ro_filter.might_contain(key1), "Filter should contain key1");
+    assert!(ro_filter.might_contain(key2), "Filter should contain key2");
+    assert!(!ro_filter.might_contain(b"other"), "Filter should not contain 'other'");
+}
+
+#[test]
+fn test_false_positive_rate() {
+    const NB_ELEM: u64 = 100_000;
+
+    let filter = create_filter::<DefaultHasher>(NB_ELEM);
+
+    // Add elements
+    for i in 0..NB_ELEM {
+        filter.add(&i);
+    }
+
+    let read_only_filter = filter.finalize();
+
+    // Test false positive rate
+    let fp_rate = read_only_filter.estimated_false_positive_rate();
+    assert!(
+        fp_rate <= FALSE_POSITIF_RATE * 1.1,
+        "False positive rate {} exceeds expected rate {} by more than 10%",
+        fp_rate,
+        FALSE_POSITIF_RATE
+    );
+}
+
+#[test]
+fn test_parallel() {
+    const NB_ELEM: u64 = 10_000;
+
+    let filter = create_filter::<DefaultHasher>(NB_ELEM);
+    let elements: Vec<u64> = (0..NB_ELEM).collect();
+
+    // Test parallel insertion
+    elements.par_iter().for_each(|item| {
+        filter.add(item);
+    });
+
+    let ro_filter = filter.finalize();
+
+    // Test parallel lookup for false negatives
+    let false_negatives: Vec<_> = elements.par_iter().filter(|&&item| !ro_filter.might_contain(&item)).collect();
+
+    assert!(false_negatives.is_empty(), "Found {} false negatives which should be impossible", false_negatives.len());
+}
+
+#[test]
+fn test_pre_calculated_hashes() {
+    const NB_ELEM: u64 = 1_000;
+
+    let filter = create_filter::<DefaultHasher>(NB_ELEM);
+    let test_item = "test_value";
+
+    // Create pre-calculated hashes
+    let pre_calc = PreCalculatedHashes::new::<DefaultHasher, _>(HASH_COUNT, &test_item);
+
+    // Test that hash count matches
+    assert_eq!(pre_calc.hash_count(), HASH_COUNT, "Pre-calculated hash count should match filter hash count");
+
+    filter.add(&test_item);
+    let ro_filter = filter.finalize();
+
+    // Test lookup with pre-calculated hashes
+    assert!(ro_filter.might_contain_hashes(&pre_calc), "Filter should find item using pre-calculated hashes");
+}
+
+#[test]
+fn test_actual_false_positive_rate() {
+    const NB_ELEM: u64 = 10_000;
+    const TEST_SAMPLES: u64 = 100_000;
+
+    let filter = create_filter::<DefaultHasher>(NB_ELEM);
+    let mut rng = thread_rng();
+
+    // Create a set of known elements
+    let mut known_elements = HashSet::new();
+    for _ in 0..NB_ELEM {
+        let value: u64 = rng.gen();
+        known_elements.insert(value);
+        filter.add(&value);
+    }
+
+    let ro_filter = filter.finalize();
+
+    // Test random elements and count false positives
+    let mut false_positives = 0;
+    for _ in 0..TEST_SAMPLES {
+        let test_value: u64 = rng.gen();
+        if !known_elements.contains(&test_value) && ro_filter.might_contain(&test_value) {
+            false_positives += 1;
+        }
+    }
+
+    let actual_fp_rate = false_positives as f64 / TEST_SAMPLES as f64;
+    println!("Actual false positive rate: {}", actual_fp_rate);
+    assert!(
+        actual_fp_rate <= FALSE_POSITIF_RATE * 1.1,
+        "Actual false positive rate {} significantly exceeds expected rate {}",
+        actual_fp_rate,
+        FALSE_POSITIF_RATE
+    );
+}
+
+#[test]
+fn test_size_alignment() {
+    // Test that the filter size is properly aligned to 64-bit boundaries
+    let filter = BloomFilter::<DefaultHasher, AtomicBitStore>::new(100, HASH_COUNT);
+    assert_eq!(filter.len() % 64, 0, "Filter size should be aligned to 64 bits");
+}
+
+#[test]
+fn test_empty_filter() {
+    let filter = create_filter::<DefaultHasher>(0);
+    let ro_filter = filter.finalize();
+
+    // Test various types with empty filter
+    assert!(!ro_filter.might_contain(&42u64));
+    assert!(!ro_filter.might_contain(&"test"));
+    assert!(!ro_filter.might_contain(&vec![1, 2, 3]));
+    assert!(!ro_filter.might_contain(&(21, "tuple")));
+}
+
+#[test]
+fn test_different_types() {
+    let filter = create_filter::<DefaultHasher>(10);
+
+    // Test adding different types
+    filter.add(&42u64);
+    filter.add(&"string");
+    filter.add(&vec![1, 2, 3]);
+    filter.add(&(21, "tuple"));
+
+    let ro_filter = filter.finalize();
+
+    assert!(ro_filter.might_contain(&42u64));
+    assert!(ro_filter.might_contain(&"string"));
+    assert!(ro_filter.might_contain(&vec![1, 2, 3]));
+    assert!(ro_filter.might_contain(&(21, "tuple")));
+}
+
+#[test]
+fn test_concurrent_reads() {
+    const NB_ELEM: u64 = 1_000;
+    const NB_THREADS: usize = 8;
+
+    let filter = create_filter::<DefaultHasher>(NB_ELEM);
+    let test_value = "concurrent_test";
+    filter.add(&test_value);
+
+    let ro_filter = filter.finalize();
+    let filter_arc = std::sync::Arc::new(ro_filter);
+
+    // Spawn multiple threads to read concurrently
+    let handles: Vec<_> = (0..NB_THREADS)
+        .map(|_| {
+            let filter_clone = std::sync::Arc::clone(&filter_arc);
+            std::thread::spawn(move || {
+                for _ in 0..1000 {
+                    assert!(filter_clone.might_contain(&test_value));
+                }
+            })
+        })
+        .collect();
+
+    // Wait for all threads to complete
+    for handle in handles {
+        handle.join().unwrap();
+    }
+}
diff --git a/crates/madara/primitives/bloom_filter/src/tests/mod.rs b/crates/madara/primitives/bloom_filter/src/tests/mod.rs
new file mode 100644
index 000000000..7b0758f06
--- /dev/null
+++ b/crates/madara/primitives/bloom_filter/src/tests/mod.rs
@@ -0,0 +1,6 @@
+#[cfg(test)]
+mod filter_tests;
+#[cfg(test)]
+mod serialization;
+#[cfg(test)]
+mod utils;
diff --git a/crates/madara/primitives/bloom_filter/src/tests/serialization.rs b/crates/madara/primitives/bloom_filter/src/tests/serialization.rs
new file mode 100644
index 000000000..1d2ed2017
--- /dev/null
+++ b/crates/madara/primitives/bloom_filter/src/tests/serialization.rs
@@ -0,0 +1,88 @@
+use crate::*;
+use bincode::{deserialize, serialize};
+use std::collections::hash_map::DefaultHasher;
+use tests::utils::create_filter;
+
+#[test]
+fn test_basic_atomic_serialization() {
+    const NB_ELEM: u64 = 3;
+
+    let filter = create_filter::<DefaultHasher>(NB_ELEM);
+
+    filter.add(&"test1");
+    filter.add(&"test2");
+    filter.add(&"test3");
+
+    let fingerprint = filter.storage().fingerprint();
+    let serialized = serialize(&filter).unwrap();
+    let deserialized: BloomFilter<DefaultHasher, AtomicBitStore> = deserialize(&serialized).unwrap();
+    assert_eq!(fingerprint, deserialized.storage().fingerprint(), "Fingerprint mismatch");
+
+    let readonly = deserialized.finalize();
+
+    assert!(readonly.might_contain(&"test1"));
+    assert!(readonly.might_contain(&"test2"));
+    assert!(readonly.might_contain(&"test3"));
+    assert!(!readonly.might_contain(&"test4"));
+}
+
+#[test]
+fn test_basic_readonly_serialization() {
+    const NB_ELEM: u64 = 1;
+
+    let filter = create_filter::<DefaultHasher>(NB_ELEM);
+
+    filter.add(&"test1");
+    let readonly = filter.finalize();
+    let fingerprint = readonly.storage().fingerprint();
+
+    // Sérialiser
+    let serialized = serialize(&readonly).unwrap();
+
+    // Désérialiser
+    let deserialized: BloomFilter<DefaultHasher, BitStore> = deserialize(&serialized).unwrap();
+
+    assert_eq!(fingerprint, deserialized.storage().fingerprint(), "Fingerprint mismatch");
+
+    assert!(deserialized.might_contain(&"test1"));
+    assert!(!deserialized.might_contain(&"test2"));
+}
+
+#[test]
+fn test_cross_type_serialization() {
+    const NB_ELEM: u64 = 3;
+
+    let filter = create_filter::<DefaultHasher>(NB_ELEM);
+
+    filter.add(&"test1");
+    filter.add(&"test2");
+
+    let serialized = serialize(&filter).unwrap();
+
+    let deserialized: BloomFilter<DefaultHasher, BitStore> = deserialize(&serialized).unwrap();
+
+    assert!(deserialized.might_contain(&"test1"));
+    assert!(deserialized.might_contain(&"test2"));
+    assert!(!deserialized.might_contain(&"test3"));
+}
+
+#[test]
+fn test_large_dataset_serialization() {
+    const NB_ELEM: u64 = 10_000;
+
+    let filter = create_filter::<DefaultHasher>(NB_ELEM);
+
+    for i in 0..NB_ELEM {
+        filter.add(&format!("test{}", i));
+    }
+
+    let serialized = serialize(&filter).unwrap();
+
+    let _atomic: BloomFilter<DefaultHasher, AtomicBitStore> = deserialize(&serialized).unwrap();
+    let readonly: BloomFilter<DefaultHasher, BitStore> = deserialize(&serialized).unwrap();
+
+    for i in 0..NB_ELEM {
+        let key = format!("test{}", i);
+        assert!(readonly.might_contain(&key));
+    }
+}
diff --git a/crates/madara/primitives/bloom_filter/src/tests/utils.rs b/crates/madara/primitives/bloom_filter/src/tests/utils.rs
new file mode 100644
index 000000000..8e3581d61
--- /dev/null
+++ b/crates/madara/primitives/bloom_filter/src/tests/utils.rs
@@ -0,0 +1,20 @@
+use std::hash::Hasher;
+
+use crate::{AtomicBitStore, BloomFilter};
+
+// Constants for Bloom filter configuration
+// Number of hash functions used in the Bloom filter.
+// The value 7 is optimal for a false positive rate of 1% (0.01).
+pub const HASH_COUNT: u8 = 7;
+
+// Number of bits per element in the Bloom filter.
+// The value 9.6 is optimal for a false positive rate of 1% (0.01).
+const BITS_PER_ELEMENT: f64 = 9.6;
+
+pub const FALSE_POSITIF_RATE: f64 = 0.01;
+
+/// Helper function to create a Bloom filter with the given number of elements
+pub fn create_filter<H: Hasher + Default>(nb_elem: u64) -> BloomFilter<H, AtomicBitStore> {
+    let size = (nb_elem as f64 * BITS_PER_ELEMENT).ceil() as u64;
+    BloomFilter::new(size, HASH_COUNT)
+}