From 1a5e2b6256838def3d993f2c913788c7dd2a9e2c Mon Sep 17 00:00:00 2001 From: houseme Date: Mon, 7 Sep 2026 23:43:42 +0800 Subject: [PATCH] test(scanner): bind segment proof generations (#7416) Co-authored-by: zhi22915 --- .../checkpoint_fixture/segment_observation.rs | 16 ++++++++++++++ crates/scanner/src/segment_invalidation.rs | 22 +++++++++++++++++++ docs/testing/scanner-checkpoint-fixture.md | 2 +- 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/crates/scanner/src/scanner_folder/tests/checkpoint_fixture/segment_observation.rs b/crates/scanner/src/scanner_folder/tests/checkpoint_fixture/segment_observation.rs index 04ffa882a..917850693 100644 --- a/crates/scanner/src/scanner_folder/tests/checkpoint_fixture/segment_observation.rs +++ b/crates/scanner/src/scanner_folder/tests/checkpoint_fixture/segment_observation.rs @@ -37,6 +37,8 @@ fn segment_proof() -> SegmentInvalidationProof { key_format: envelope.key_format, baseline_scan_plan_digest: envelope.baseline_scan_plan_digest, process_epoch: envelope.process_epoch, + generation_start: envelope.generation_start, + generation_end: envelope.generation_end, durable_producer_identity: true, invalidation_domain: SegmentInvalidationDomain::LocalSingleSet, distributed_ec_invalidation: false, @@ -126,6 +128,20 @@ fn segment_observation_trusted_proposal_requires_identity_and_complete_producer_ Err(SegmentInvalidationError::InvalidProof) ); + let mut wrong_generation_start = proof.clone(); + wrong_generation_start.generation_start = wrong_generation_start.generation_start.saturating_sub(1); + assert_eq!( + admit_segment_invalidation(&envelope, &wrong_generation_start, ["hot/one"]), + Err(SegmentInvalidationError::InvalidProof) + ); + + let mut wrong_generation_end = proof.clone(); + wrong_generation_end.generation_end = wrong_generation_end.generation_end.saturating_add(1); + assert_eq!( + admit_segment_invalidation(&envelope, &wrong_generation_end, ["hot/one"]), + Err(SegmentInvalidationError::InvalidProof) + ); + let mut no_durable_identity = proof.clone(); no_durable_identity.durable_producer_identity = false; assert_eq!( diff --git a/crates/scanner/src/segment_invalidation.rs b/crates/scanner/src/segment_invalidation.rs index 0d5c64ecf..f2a74658c 100644 --- a/crates/scanner/src/segment_invalidation.rs +++ b/crates/scanner/src/segment_invalidation.rs @@ -77,6 +77,8 @@ pub struct SegmentInvalidationProof { pub key_format: u16, pub baseline_scan_plan_digest: DataUsageScanPlanDigest, pub process_epoch: String, + pub generation_start: u64, + pub generation_end: u64, pub durable_producer_identity: bool, pub invalidation_domain: SegmentInvalidationDomain, pub distributed_ec_invalidation: bool, @@ -108,11 +110,15 @@ fn validate_segment_invalidation_proof( || envelope.baseline_scan_plan_digest != proof.baseline_scan_plan_digest || envelope.process_epoch.is_empty() || envelope.process_epoch != proof.process_epoch + || envelope.generation_start != proof.generation_start + || envelope.generation_end != proof.generation_end || !proof.durable_producer_identity || !proof.cold_zero_walk_oracle || (proof.invalidation_domain == SegmentInvalidationDomain::DistributedEc && !proof.distributed_ec_invalidation) || envelope.generation_start == 0 || envelope.generation_end < envelope.generation_start + || proof.generation_start == 0 + || proof.generation_end < proof.generation_start || envelope.restart_gap || envelope.overflow || !SegmentInvalidationProducer::REQUIRED @@ -190,6 +196,8 @@ mod tests { key_format: envelope.key_format, baseline_scan_plan_digest: envelope.baseline_scan_plan_digest, process_epoch: envelope.process_epoch, + generation_start: envelope.generation_start, + generation_end: envelope.generation_end, durable_producer_identity: true, invalidation_domain: SegmentInvalidationDomain::LocalSingleSet, distributed_ec_invalidation: false, @@ -241,6 +249,20 @@ mod tests { Err(SegmentInvalidationError::InvalidProof) ); + let mut wrong_generation_start = proof.clone(); + wrong_generation_start.generation_start = wrong_generation_start.generation_start.saturating_sub(1); + assert_eq!( + admit_segment_invalidation(&envelope, &wrong_generation_start, ["hot/one"]), + Err(SegmentInvalidationError::InvalidProof) + ); + + let mut wrong_generation_end = proof.clone(); + wrong_generation_end.generation_end = wrong_generation_end.generation_end.saturating_add(1); + assert_eq!( + admit_segment_invalidation(&envelope, &wrong_generation_end, ["hot/one"]), + Err(SegmentInvalidationError::InvalidProof) + ); + let mut no_durable_identity = proof.clone(); no_durable_identity.durable_producer_identity = false; assert_eq!( diff --git a/docs/testing/scanner-checkpoint-fixture.md b/docs/testing/scanner-checkpoint-fixture.md index 60a32e80d..a11a1b7f9 100644 --- a/docs/testing/scanner-checkpoint-fixture.md +++ b/docs/testing/scanner-checkpoint-fixture.md @@ -64,7 +64,7 @@ The nested `segment_observation` fixture compares diagnostic on/off runs of the Entry/byte overflow and malformed keys reject the fixture proposal. Missing producers, process restarts, event gaps, and compacted child coverage remain **unverified production capabilities**, not simulated success cases in this fixture. Mainline bucket dirty generations and hashed metadata-cache invalidation stripes are not an exact, replayable object-key stream. The open [prefix reuse proposal #7208](https://github.com/rustfs/rustfs/pull/7208) is a separate candidate implementation; these tests neither import its hint map nor activate its skip path. -The ECStore `segment_observation_equal_size_mutations_retire_metadata_generation` test uses the existing exact-key, test-only invalidation probe and actual owner operations. A same-length PUT must change the returned body and ETag while retiring the old generation; metadata-only PUT must change returned metadata and retire the old generation while size and ETag remain equal. Setup uses the existing full-fanout cache-priming helper; the observed mutations use normal owner locking. This is focused producer evidence, not an end-to-end connection between the owner probe and scanner range selection. The existing semantic mutation matrix covers additional owner entry points separately. +The ECStore `segment_observation_equal_size_mutations_retire_metadata_generation` test uses the existing exact-key, test-only invalidation probe and actual owner operations. A same-length PUT must change the returned body and ETag while retiring the old generation; metadata-only PUT must change returned metadata and retire the old generation while size and ETag remain equal. Setup uses the existing full-fanout cache-priming helper; the observed mutations use normal owner locking. This is focused producer evidence, not an end-to-end connection between the owner probe and scanner range selection. The segment invalidation proof is bound to the same generation window as the observed envelope, so an old distributed or cold-walk proof cannot authorize a later mutation range. The existing semantic mutation matrix covers additional owner entry points separately. ```sh cargo test -p rustfs-scanner --lib segment_observation -- --list