Make tree ancestry acceptance test deterministic

The test used Identity::generate() for the signing node while pinning
the fixed root to node_addr byte[0]=0x01. About 2/256 random identities
have byte[0] <= 0x01, which made the generated node_addr the path
minimum and triggered AncestryRootNotMinimum. Regenerate the identity
until its node_addr is numerically larger than the fixed parent and
root, so the test matches the preconditions it asserts.
This commit is contained in:
Johnathan Corgan
2026-04-22 01:17:04 +00:00
parent db4b32110c
commit de8db82614
2 changed files with 28 additions and 22 deletions

View File

@@ -13,28 +13,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
and `.deb` packages on `v*` tag push, with SHA-256 checksums
- AUR publish workflow for tagged stable releases
### Security
### Changed
- Bloom filter poisoning defense. Reject inbound FilterAnnounce
messages whose false-positive rate exceeds a configurable cap
(`node.bloom.max_inbound_fpr`, default 0.05). Previously a peer
running a modified build could send an all-ones filter, causing
(1) lookup attraction / black-hole routing for unknown targets,
(2) aggregation contamination as the poisoned bits propagated one
hop per announce tick via strict-OR merging, and (3) mesh-size
estimate blowup to `f64::INFINITY`. Rejection is silent on the
wire; rejected announces log at WARN and increment a new
`bloom.fill_exceeded` counter. The peer's prior accepted filter
and sequence number are preserved on rejection so a single bad
announce cannot wipe a peer's contribution to aggregation.
An independent self-plausibility WARN fires (rate-limited to once
per 60s) if our own outgoing filter ever exceeds the cap,
surfacing aggregation drift or ingress-check bypasses.
`BloomFilter::estimated_count` now returns `Option<f64>` and
returns `None` for saturated filters, preventing `f64::INFINITY`
from propagating into mesh-size estimates. The node-level
`estimated_mesh_size` field (already `Option<u64>`) propagates
`None` when any contributing filter is above cap.
- Validate bloom filter fill ratio on FilterAnnounce ingress.
Inbound FilterAnnounce messages whose derived false-positive
rate exceeds `node.bloom.max_inbound_fpr` (new config field,
default 0.05) are rejected silently on the wire, logged at WARN,
and counted in a new `bloom.fill_exceeded` counter. A
rate-limited WARN also fires if our own outgoing filter's FPR
exceeds the cap. `BloomFilter::estimated_count` now takes
`max_fpr` and returns `Option<f64>`, returning `None` for
saturated filters; this propagates through `compute_mesh_size`
into `estimated_mesh_size` (already `Option<u64>`)
### Fixed
@@ -71,6 +61,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
tree specification. The receive path now verifies that the
ancestry is structurally consistent with the signed parent
declaration before mutating tree state.
- Make the tree ancestry acceptance unit test deterministic.
`test_tree_announce_validate_semantics_accepts_valid_non_root`
generated a random signing identity while pinning the fixed root
to `node_addr[0] = 0x01`; about 2 in 256 random identities were
numerically smaller than the claimed root, triggering
`AncestryRootNotMinimum`. The test now regenerates the identity
until its `node_addr` is strictly larger than both the fixed
parent and root.
## [0.2.0] - 2026-03-22

View File

@@ -439,7 +439,15 @@ mod tests {
fn test_tree_announce_validate_semantics_accepts_valid_non_root() {
use crate::identity::Identity;
let identity = Identity::generate();
// Regenerate until the random identity's node_addr is numerically
// larger than both fixed parent (02:..) and root (01:..), so the
// root-minimum invariant holds deterministically.
let identity = loop {
let id = Identity::generate();
if id.node_addr().as_bytes()[0] > 1 {
break id;
}
};
let node_addr = *identity.node_addr();
let parent = make_node_addr(2);
let root = make_node_addr(1);