mirror of
https://github.com/jmcorgan/fips.git
synced 2026-07-22 07:48:26 +00:00
Bloom module test coverage, benchmarks, and design doc corrections
Testing: - Add 14 bloom module tests (39 total): from_bytes error paths, from_slice round-trip, insert_bytes/contains_bytes, estimated_count saturation, Default/Debug traits, mark_changed_peers cascade prevention (4 scenarios), remove_peer_state, record_sent_filter, leaf_dependents accessor. Benchmarks: - Add criterion benchmark suite for bloom filter hot-path operations: insert, contains, merge, from_bytes, fill_ratio, estimated_count, equality, compute_outgoing_filter, mark_changed_peers, base_filter. Parameterized over realistic occupancy levels and peer counts. Design doc corrections: - Fix visited bloom filter hash_count in gossip protocol doc (7→5, matching code for 256-byte filter occupancy). - Correct LookupResponse proof signature scope in fips-routing.md and fips-gossip-protocol.md: proof covers (request_id || target) only — coords excluded to survive tree reconvergence during lookup RTT.
This commit is contained in:
359
Cargo.lock
generated
359
Cargo.lock
generated
@@ -21,6 +21,12 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[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.21"
|
||||
@@ -107,6 +113,12 @@ version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
||||
|
||||
[[package]]
|
||||
name = "bech32"
|
||||
version = "0.11.1"
|
||||
@@ -157,6 +169,12 @@ dependencies = [
|
||||
"piper",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
@@ -189,6 +207,12 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cast"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.54"
|
||||
@@ -235,6 +259,33 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[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"
|
||||
@@ -310,12 +361,73 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[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",
|
||||
"num-traits",
|
||||
"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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
@@ -359,6 +471,12 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
@@ -415,6 +533,7 @@ dependencies = [
|
||||
"bech32",
|
||||
"chacha20poly1305",
|
||||
"clap",
|
||||
"criterion",
|
||||
"dirs",
|
||||
"futures",
|
||||
"hex",
|
||||
@@ -567,6 +686,17 @@ dependencies = [
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crunchy",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.1"
|
||||
@@ -579,6 +709,12 @@ version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
@@ -637,18 +773,48 @@ version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
@@ -815,6 +981,15 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
@@ -827,6 +1002,12 @@ version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
version = "11.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.1"
|
||||
@@ -874,6 +1055,34 @@ dependencies = [
|
||||
"futures-io",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "plotters"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"plotters-backend",
|
||||
"plotters-svg",
|
||||
"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-svg"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
|
||||
dependencies = [
|
||||
"plotters-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "poly1305"
|
||||
version = "0.8.0"
|
||||
@@ -948,6 +1157,26 @@ dependencies = [
|
||||
"getrandom 0.2.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.5.2"
|
||||
@@ -959,6 +1188,18 @@ dependencies = [
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.13"
|
||||
@@ -1007,12 +1248,27 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "secp256k1"
|
||||
version = "0.30.0"
|
||||
@@ -1063,6 +1319,19 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.149"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.9.34+deprecated"
|
||||
@@ -1228,6 +1497,16 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[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 = "tokio"
|
||||
version = "1.49.0"
|
||||
@@ -1396,6 +1675,16 @@ version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
@@ -1411,6 +1700,70 @@ dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
@@ -1630,3 +1983,9 @@ name = "zeroize"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||
|
||||
@@ -27,3 +27,8 @@ simple-dns = "0.9"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.15"
|
||||
criterion = { version = "0.5", features = ["html_reports"] }
|
||||
|
||||
[[bench]]
|
||||
name = "bloom"
|
||||
harness = false
|
||||
|
||||
266
benches/bloom.rs
Normal file
266
benches/bloom.rs
Normal file
@@ -0,0 +1,266 @@
|
||||
//! Benchmarks for the bloom filter module.
|
||||
//!
|
||||
//! Run with: cargo bench --bench bloom
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||
use fips::bloom::{BloomFilter, BloomState};
|
||||
use fips::NodeAddr;
|
||||
use std::collections::HashMap;
|
||||
|
||||
fn make_node_addr(val: u16) -> NodeAddr {
|
||||
let mut bytes = [0u8; 16];
|
||||
bytes[0..2].copy_from_slice(&val.to_le_bytes());
|
||||
NodeAddr::from_bytes(bytes)
|
||||
}
|
||||
|
||||
/// Pre-populate a filter with `n` entries for realistic benchmarks.
|
||||
fn populated_filter(n: u16) -> BloomFilter {
|
||||
let mut filter = BloomFilter::new();
|
||||
for i in 0..n {
|
||||
filter.insert(&make_node_addr(i));
|
||||
}
|
||||
filter
|
||||
}
|
||||
|
||||
// ===== BloomFilter Benchmarks =====
|
||||
|
||||
fn bench_insert(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("bloom_insert");
|
||||
|
||||
let node = make_node_addr(9999);
|
||||
|
||||
// Insert into empty filter
|
||||
group.bench_function("empty", |b| {
|
||||
b.iter(|| {
|
||||
let mut filter = BloomFilter::new();
|
||||
filter.insert(black_box(&node));
|
||||
})
|
||||
});
|
||||
|
||||
// Insert into a filter with 400 entries (typical occupancy)
|
||||
let base = populated_filter(400);
|
||||
group.bench_function("400_entries", |b| {
|
||||
b.iter(|| {
|
||||
let mut filter = base.clone();
|
||||
filter.insert(black_box(&node));
|
||||
})
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_contains(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("bloom_contains");
|
||||
|
||||
let present = make_node_addr(200);
|
||||
let absent = make_node_addr(9999);
|
||||
|
||||
for &count in &[100, 400, 800] {
|
||||
let filter = populated_filter(count);
|
||||
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("hit", count),
|
||||
&count,
|
||||
|b, _| {
|
||||
b.iter(|| filter.contains(black_box(&present)))
|
||||
},
|
||||
);
|
||||
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("miss", count),
|
||||
&count,
|
||||
|b, _| {
|
||||
b.iter(|| filter.contains(black_box(&absent)))
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_merge(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("bloom_merge");
|
||||
|
||||
for &count in &[100, 400, 800] {
|
||||
let filter_a = populated_filter(count);
|
||||
let filter_b = populated_filter(count + 500); // different entries
|
||||
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(count),
|
||||
&count,
|
||||
|b, _| {
|
||||
b.iter(|| {
|
||||
let mut target = filter_a.clone();
|
||||
target.merge(black_box(&filter_b)).unwrap();
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_from_bytes(c: &mut Criterion) {
|
||||
let filter = populated_filter(400);
|
||||
let bytes = filter.as_bytes().to_vec();
|
||||
let hash_count = filter.hash_count();
|
||||
|
||||
c.bench_function("bloom_from_bytes", |b| {
|
||||
b.iter(|| {
|
||||
BloomFilter::from_bytes(black_box(bytes.clone()), black_box(hash_count)).unwrap()
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_fill_ratio(c: &mut Criterion) {
|
||||
let filter = populated_filter(400);
|
||||
|
||||
c.bench_function("bloom_fill_ratio", |b| {
|
||||
b.iter(|| filter.fill_ratio())
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_estimated_count(c: &mut Criterion) {
|
||||
let filter = populated_filter(400);
|
||||
|
||||
c.bench_function("bloom_estimated_count", |b| {
|
||||
b.iter(|| filter.estimated_count())
|
||||
});
|
||||
}
|
||||
|
||||
fn bench_equality(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("bloom_equality");
|
||||
|
||||
let filter_a = populated_filter(400);
|
||||
let filter_b = filter_a.clone();
|
||||
let mut filter_c = filter_a.clone();
|
||||
filter_c.insert(&make_node_addr(9999));
|
||||
|
||||
group.bench_function("equal", |b| {
|
||||
b.iter(|| black_box(&filter_a) == black_box(&filter_b))
|
||||
});
|
||||
|
||||
group.bench_function("not_equal", |b| {
|
||||
b.iter(|| black_box(&filter_a) == black_box(&filter_c))
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
// ===== BloomState Benchmarks =====
|
||||
|
||||
fn bench_compute_outgoing_filter(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("bloom_compute_outgoing");
|
||||
|
||||
for &peer_count in &[2, 5, 10, 20] {
|
||||
let my_node = make_node_addr(0);
|
||||
let mut state = BloomState::new(my_node);
|
||||
state.add_leaf_dependent(make_node_addr(1));
|
||||
|
||||
// Create peer filters with realistic content
|
||||
let mut peer_filters = HashMap::new();
|
||||
let mut peer_addrs = Vec::new();
|
||||
for i in 0..peer_count {
|
||||
let peer = make_node_addr(100 + i);
|
||||
peer_addrs.push(peer);
|
||||
let mut pf = BloomFilter::new();
|
||||
// Each peer knows about ~50 nodes
|
||||
for j in 0..50 {
|
||||
pf.insert(&make_node_addr(1000 + i * 50 + j));
|
||||
}
|
||||
peer_filters.insert(peer, pf);
|
||||
}
|
||||
|
||||
let exclude = peer_addrs[0];
|
||||
|
||||
group.bench_with_input(
|
||||
BenchmarkId::from_parameter(peer_count),
|
||||
&peer_count,
|
||||
|b, _| {
|
||||
b.iter(|| {
|
||||
state.compute_outgoing_filter(
|
||||
black_box(&exclude),
|
||||
black_box(&peer_filters),
|
||||
)
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_mark_changed_peers(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("bloom_mark_changed_peers");
|
||||
|
||||
for &peer_count in &[2, 5, 10, 20] {
|
||||
let my_node = make_node_addr(0);
|
||||
let mut state = BloomState::new(my_node);
|
||||
|
||||
let mut peer_filters = HashMap::new();
|
||||
let mut peer_addrs = Vec::new();
|
||||
for i in 0..peer_count {
|
||||
let peer = make_node_addr(100 + i);
|
||||
peer_addrs.push(peer);
|
||||
let mut pf = BloomFilter::new();
|
||||
for j in 0..50 {
|
||||
pf.insert(&make_node_addr(1000 + i * 50 + j));
|
||||
}
|
||||
peer_filters.insert(peer, pf);
|
||||
}
|
||||
|
||||
// Record initial outgoing filters so the benchmark measures
|
||||
// the change-detection comparison, not just "never sent"
|
||||
for &peer in &peer_addrs {
|
||||
let outgoing = state.compute_outgoing_filter(&peer, &peer_filters);
|
||||
state.record_sent_filter(peer, outgoing);
|
||||
}
|
||||
|
||||
let source = peer_addrs[0];
|
||||
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("steady_state", peer_count),
|
||||
&peer_count,
|
||||
|b, _| {
|
||||
b.iter(|| {
|
||||
// Clone state so mark_changed_peers doesn't accumulate
|
||||
let mut s = state.clone();
|
||||
s.mark_changed_peers(
|
||||
black_box(&source),
|
||||
black_box(&peer_addrs),
|
||||
black_box(&peer_filters),
|
||||
);
|
||||
})
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn bench_base_filter(c: &mut Criterion) {
|
||||
let my_node = make_node_addr(0);
|
||||
let mut state = BloomState::new(my_node);
|
||||
for i in 1..=10 {
|
||||
state.add_leaf_dependent(make_node_addr(i));
|
||||
}
|
||||
|
||||
c.bench_function("bloom_base_filter_10_deps", |b| {
|
||||
b.iter(|| state.base_filter())
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
bench_insert,
|
||||
bench_contains,
|
||||
bench_merge,
|
||||
bench_from_bytes,
|
||||
bench_fill_ratio,
|
||||
bench_estimated_count,
|
||||
bench_equality,
|
||||
bench_compute_outgoing_filter,
|
||||
bench_mark_changed_peers,
|
||||
bench_base_filter,
|
||||
);
|
||||
criterion_main!(benches);
|
||||
@@ -386,8 +386,10 @@ LookupResponse {
|
||||
**target_coords**: The target's current tree coordinates. This is the primary
|
||||
payload — cached by the originator to enable routing to the target.
|
||||
|
||||
**proof**: Target's signature over `(request_id || target || target_coords)`.
|
||||
Prevents malicious nodes from claiming reachability and blackholing traffic.
|
||||
**proof**: Target's signature over `(request_id || target)`. Prevents
|
||||
malicious nodes from claiming reachability and blackholing traffic.
|
||||
Coordinates are excluded from the proof to avoid invalidation during
|
||||
tree reconvergence (see [fips-routing.md](fips-routing.md) §2.4).
|
||||
|
||||
### 5.3 Routing
|
||||
|
||||
@@ -680,7 +682,7 @@ PLAINTEXT BYTES:
|
||||
40 ← ttl = 64
|
||||
04 00 ← origin_coords_count = 4
|
||||
[16 bytes] × 4 ← origin's ancestry (64 bytes)
|
||||
07 ← visited hash_count = 7
|
||||
05 ← visited hash_count = 5
|
||||
[256 bytes visited bloom] ← nodes that have seen this request
|
||||
|
||||
Total: 1 + 8 + 16 + 16 + 1 + 2 + 64 + 1 + 256 = 365 bytes
|
||||
@@ -708,8 +710,8 @@ Returns target's coordinates to the requester.
|
||||
│ │ ... │ proof │ 64 bytes │ Target's signature │ │
|
||||
│ └────────┴──────────────────┴───────────┴───────────────────────────────┘ │
|
||||
│ │
|
||||
│ Proof signature covers: (request_id || target || target_coords) │
|
||||
│ Prevents malicious nodes from claiming reachability for any target. │
|
||||
│ Proof signature covers: (request_id || target) │
|
||||
│ Coords excluded to survive tree reconvergence during lookup RTT. │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
@@ -301,9 +301,17 @@ Each router forwards toward the origin using tree distance.
|
||||
### Security
|
||||
|
||||
The target signs the LookupResponse with a proof covering
|
||||
`(request_id || target || target_coords)`. Without this signature, a malicious
|
||||
node could claim reachability for any target and blackhole traffic. The
|
||||
signature proves the target authorized the route.
|
||||
`(request_id || target)`. Without this signature, a malicious node could
|
||||
claim reachability for any target and blackhole traffic. The signature
|
||||
proves the target authorized the route.
|
||||
|
||||
Note: `target_coords` are intentionally excluded from the proof. Binding
|
||||
coordinates would invalidate the signature whenever the spanning tree
|
||||
reconverges (parent switch, root change), causing valid responses to be
|
||||
rejected if tree topology shifts during the lookup RTT. Since coordinates
|
||||
are ephemeral routing hints and data integrity is protected by the
|
||||
session layer, coordinate tampering by a transit node only causes routing
|
||||
inefficiency, not a security breach.
|
||||
|
||||
### Caching
|
||||
|
||||
|
||||
@@ -190,6 +190,78 @@ fn test_bloom_filter_equality() {
|
||||
assert_eq!(filter1, filter2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bloom_filter_from_bytes_empty() {
|
||||
let result = BloomFilter::from_bytes(vec![], 5);
|
||||
assert!(matches!(result, Err(BloomError::SizeNotByteAligned(0))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bloom_filter_from_bytes_zero_hash_count() {
|
||||
let result = BloomFilter::from_bytes(vec![0u8; 128], 0);
|
||||
assert!(matches!(result, Err(BloomError::ZeroHashCount)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bloom_filter_from_slice() {
|
||||
let mut original = BloomFilter::new();
|
||||
original.insert(&make_node_addr(42));
|
||||
let bytes = original.as_bytes();
|
||||
|
||||
let restored = BloomFilter::from_slice(bytes, original.hash_count()).unwrap();
|
||||
assert_eq!(original, restored);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bloom_filter_insert_bytes_contains_bytes() {
|
||||
let mut filter = BloomFilter::new();
|
||||
let data1 = b"hello world";
|
||||
let data2 = b"goodbye";
|
||||
|
||||
assert!(!filter.contains_bytes(data1));
|
||||
|
||||
filter.insert_bytes(data1);
|
||||
assert!(filter.contains_bytes(data1));
|
||||
assert!(!filter.contains_bytes(data2));
|
||||
|
||||
filter.insert_bytes(data2);
|
||||
assert!(filter.contains_bytes(data1));
|
||||
assert!(filter.contains_bytes(data2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bloom_filter_estimated_count_saturated() {
|
||||
// Create a small filter with all bits set
|
||||
let bytes = vec![0xFF; 8]; // all bits set
|
||||
let filter = BloomFilter::from_bytes(bytes, 3).unwrap();
|
||||
|
||||
assert!(filter.estimated_count().is_infinite());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bloom_filter_default() {
|
||||
let default: BloomFilter = Default::default();
|
||||
let explicit = BloomFilter::new();
|
||||
assert_eq!(default, explicit);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bloom_filter_debug_format() {
|
||||
let mut filter = BloomFilter::new();
|
||||
let debug = format!("{:?}", filter);
|
||||
assert!(debug.contains("BloomFilter"));
|
||||
assert!(debug.contains("8192"));
|
||||
assert!(debug.contains("hash_count"));
|
||||
|
||||
// With some entries
|
||||
for i in 0..10 {
|
||||
filter.insert(&make_node_addr(i));
|
||||
}
|
||||
let debug = format!("{:?}", filter);
|
||||
assert!(debug.contains("fill_ratio"));
|
||||
assert!(debug.contains("est_count"));
|
||||
}
|
||||
|
||||
// ===== BloomState Tests =====
|
||||
|
||||
#[test]
|
||||
@@ -338,3 +410,165 @@ fn test_bloom_state_compute_outgoing_filter() {
|
||||
assert!(outgoing2.contains(&make_node_addr(100))); // from peer1
|
||||
assert!(outgoing2.contains(&make_node_addr(101))); // from peer1
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bloom_state_leaf_dependents_accessor() {
|
||||
let node = make_node_addr(0);
|
||||
let mut state = BloomState::new(node);
|
||||
|
||||
let leaf1 = make_node_addr(1);
|
||||
let leaf2 = make_node_addr(2);
|
||||
|
||||
state.add_leaf_dependent(leaf1);
|
||||
state.add_leaf_dependent(leaf2);
|
||||
|
||||
let deps = state.leaf_dependents();
|
||||
assert!(deps.contains(&leaf1));
|
||||
assert!(deps.contains(&leaf2));
|
||||
assert!(!deps.contains(&make_node_addr(99)));
|
||||
assert_eq!(deps.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bloom_state_record_sent_filter() {
|
||||
let node = make_node_addr(0);
|
||||
let mut state = BloomState::new(node);
|
||||
|
||||
let peer = make_node_addr(1);
|
||||
let mut filter = BloomFilter::new();
|
||||
filter.insert(&make_node_addr(42));
|
||||
|
||||
// Record a sent filter, then mark_changed_peers should detect no change
|
||||
// when the outgoing filter matches what was recorded
|
||||
state.record_sent_filter(peer, filter);
|
||||
|
||||
// Compute what would be sent to peer (just our own node, no peer filters)
|
||||
let peer_filters = HashMap::new();
|
||||
let peer_addrs = vec![peer];
|
||||
state.mark_changed_peers(&make_node_addr(99), &peer_addrs, &peer_filters);
|
||||
|
||||
// Outgoing filter (just self) differs from recorded (self + node 42),
|
||||
// so peer should be marked for update
|
||||
assert!(state.needs_update(&peer));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bloom_state_remove_peer_state() {
|
||||
let node = make_node_addr(0);
|
||||
let mut state = BloomState::new(node);
|
||||
|
||||
let peer = make_node_addr(1);
|
||||
|
||||
// Populate all three internal maps for this peer
|
||||
state.mark_update_needed(peer);
|
||||
state.record_update_sent(peer, 1000);
|
||||
state.mark_update_needed(peer); // re-mark after send
|
||||
let filter = BloomFilter::new();
|
||||
state.record_sent_filter(peer, filter);
|
||||
|
||||
assert!(state.needs_update(&peer));
|
||||
|
||||
// Remove all peer state
|
||||
state.remove_peer_state(&peer);
|
||||
|
||||
// Pending updates cleared
|
||||
assert!(!state.needs_update(&peer));
|
||||
|
||||
// Debounce state cleared — should be able to send immediately
|
||||
state.mark_update_needed(peer);
|
||||
assert!(state.should_send_update(&peer, 0));
|
||||
|
||||
// Sent filter cleared — mark_changed_peers should treat as "never sent"
|
||||
state.clear_pending_updates();
|
||||
let peer_filters = HashMap::new();
|
||||
let peer_addrs = vec![peer];
|
||||
state.mark_changed_peers(&make_node_addr(99), &peer_addrs, &peer_filters);
|
||||
assert!(state.needs_update(&peer)); // never sent → must send
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bloom_state_mark_changed_peers_never_sent() {
|
||||
let node = make_node_addr(0);
|
||||
let mut state = BloomState::new(node);
|
||||
|
||||
let peer1 = make_node_addr(1);
|
||||
let peer2 = make_node_addr(2);
|
||||
|
||||
let peer_filters = HashMap::new();
|
||||
let peer_addrs = vec![peer1, peer2];
|
||||
|
||||
// No filters ever sent — all peers should be marked
|
||||
state.mark_changed_peers(&make_node_addr(99), &peer_addrs, &peer_filters);
|
||||
|
||||
assert!(state.needs_update(&peer1));
|
||||
assert!(state.needs_update(&peer2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bloom_state_mark_changed_peers_unchanged() {
|
||||
let node = make_node_addr(0);
|
||||
let mut state = BloomState::new(node);
|
||||
|
||||
let peer1 = make_node_addr(1);
|
||||
let peer2 = make_node_addr(2);
|
||||
let peer_filters = HashMap::new();
|
||||
let peer_addrs = vec![peer1, peer2];
|
||||
|
||||
// Compute and record what would be sent to each peer
|
||||
let outgoing1 = state.compute_outgoing_filter(&peer1, &peer_filters);
|
||||
let outgoing2 = state.compute_outgoing_filter(&peer2, &peer_filters);
|
||||
state.record_sent_filter(peer1, outgoing1);
|
||||
state.record_sent_filter(peer2, outgoing2);
|
||||
|
||||
// Nothing changed — no peers should be marked
|
||||
state.mark_changed_peers(&make_node_addr(99), &peer_addrs, &peer_filters);
|
||||
|
||||
assert!(!state.needs_update(&peer1));
|
||||
assert!(!state.needs_update(&peer2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bloom_state_mark_changed_peers_one_changed() {
|
||||
let node = make_node_addr(0);
|
||||
let mut state = BloomState::new(node);
|
||||
|
||||
let peer1 = make_node_addr(1);
|
||||
let peer2 = make_node_addr(2);
|
||||
let peer_filters = HashMap::new();
|
||||
let peer_addrs = vec![peer1, peer2];
|
||||
|
||||
// Record current outgoing filters for both peers
|
||||
let outgoing1 = state.compute_outgoing_filter(&peer1, &peer_filters);
|
||||
let outgoing2 = state.compute_outgoing_filter(&peer2, &peer_filters);
|
||||
state.record_sent_filter(peer1, outgoing1);
|
||||
state.record_sent_filter(peer2, outgoing2);
|
||||
|
||||
// Now peer1 sends us a filter with new entries
|
||||
let mut inbound_from_peer1 = BloomFilter::new();
|
||||
inbound_from_peer1.insert(&make_node_addr(100));
|
||||
let mut updated_peer_filters = HashMap::new();
|
||||
updated_peer_filters.insert(peer1, inbound_from_peer1);
|
||||
|
||||
// mark_changed_peers triggered by receiving from peer1
|
||||
state.mark_changed_peers(&peer1, &peer_addrs, &updated_peer_filters);
|
||||
|
||||
// peer1 is excluded (it's the source), peer2's outgoing changed
|
||||
// (now includes peer1's entries via split-horizon)
|
||||
assert!(!state.needs_update(&peer1));
|
||||
assert!(state.needs_update(&peer2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bloom_state_mark_changed_peers_excludes_source() {
|
||||
let node = make_node_addr(0);
|
||||
let mut state = BloomState::new(node);
|
||||
|
||||
let peer1 = make_node_addr(1);
|
||||
let peer_filters = HashMap::new();
|
||||
let peer_addrs = vec![peer1];
|
||||
|
||||
// peer1 is both the source and the only peer — should be skipped
|
||||
state.mark_changed_peers(&peer1, &peer_addrs, &peer_filters);
|
||||
|
||||
assert!(!state.needs_update(&peer1));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user