commit b82931ce699202529c35e3a9d21a2be80a533695
parent f74e8272809e9b65863a8b69cda74fbf8f511b91
Author: triesap <tyson@radroots.org>
Date: Tue, 28 Jul 2026 10:48:36 +0000
event_store: cover transition corruption boundaries
- reject partial retracted and cause references through the public feed
- exercise malformed NIP-09 suppression identities
- prove valid-but-wrong coordinate authority fails closed
- validate corrupt prior lineage after cursor resume
Diffstat:
1 file changed, 105 insertions(+), 0 deletions(-)
diff --git a/crates/event_store/src/store.rs b/crates/event_store/src/store.rs
@@ -9297,6 +9297,14 @@ CREATE TABLE aux.event_transport_observation (event_id TEXT);",
"UPDATE radroots_event_store_addressable_head_transition SET visible_event_seq = NULL",
),
(
+ "partial retracted identity",
+ "UPDATE radroots_event_store_addressable_head_transition SET retracted_event_id = raw_head_event_id",
+ ),
+ (
+ "partial cause identity",
+ "UPDATE radroots_event_store_addressable_head_transition SET cause_event_seq = NULL",
+ ),
+ (
"admission enum",
"UPDATE radroots_event_store_addressable_head_transition SET admission_status = 'invalid'",
),
@@ -9309,6 +9317,14 @@ CREATE TABLE aux.event_transport_observation (event_id TEXT);",
"UPDATE radroots_event_store_addressable_head_transition SET nip09_reason = NULL",
),
(
+ "event suppression request id",
+ "UPDATE radroots_event_store_addressable_head_transition SET event_reference_request_id = 'invalid'",
+ ),
+ (
+ "address suppression request id",
+ "UPDATE radroots_event_store_addressable_head_transition SET address_reference_request_id = 'invalid'",
+ ),
+ (
"raw-head decision enum",
"UPDATE radroots_event_store_addressable_head_transition SET raw_head_decision = 'invalid'",
),
@@ -9369,6 +9385,44 @@ CREATE TABLE aux.event_transport_observation (event_id TEXT);",
}
#[tokio::test]
+ async fn addressable_transition_feed_rejects_valid_but_wrong_coordinate_authority() {
+ let store = food_availability_audit_corruption_store().await;
+ let mut connection = store.pool().acquire().await.expect("trusted connection");
+ sqlx::query("DROP TRIGGER radroots_event_store_addressable_transition_update_guard")
+ .execute(&mut *connection)
+ .await
+ .expect("trusted transition guard removal");
+ sqlx::query("PRAGMA foreign_keys = OFF")
+ .execute(&mut *connection)
+ .await
+ .expect("disable trusted foreign-key enforcement");
+ let other_pubkey = alternate_keys().public_key().to_hex();
+ sqlx::query(
+ "UPDATE radroots_event_store_addressable_head_transition SET pubkey = ? WHERE transition_seq = 1",
+ )
+ .bind(other_pubkey)
+ .execute(&mut *connection)
+ .await
+ .expect("trusted coordinate-authority corruption");
+ sqlx::query("PRAGMA foreign_keys = ON")
+ .execute(&mut *connection)
+ .await
+ .expect("restore foreign-key enforcement");
+ drop(connection);
+
+ let scope = crate::RadrootsAddressableTransitionScopeV1::food_availability();
+ let error = store
+ .addressable_transition_page_v1(&scope, None, 64)
+ .await
+ .expect_err("wrong coordinate authority must fail public feed read");
+ assert!(matches!(
+ error,
+ RadrootsEventStoreError::AddressableTransitionCorruption { ref reason }
+ if reason.contains("does not match transition coordinate")
+ ));
+ }
+
+ #[tokio::test]
async fn addressable_transition_feed_rejects_source_authority_drift() {
for (label, mutations, expected_class) in [
(
@@ -9672,6 +9726,57 @@ CREATE TABLE aux.event_transport_observation (event_id TEXT);",
}
#[tokio::test]
+ async fn addressable_transition_feed_rejects_corrupt_prior_reference_after_cursor() {
+ let store = replacement_transition_corruption_store().await;
+ let scope = crate::RadrootsAddressableTransitionScopeV1::food_availability();
+ let first = store
+ .addressable_transition_page_v1(&scope, None, 1)
+ .await
+ .expect("first transition page");
+ let cursor = first.next_cursor().clone();
+ assert_eq!(cursor.last_transition_seq(), 1);
+
+ let mut connection = store.pool().acquire().await.expect("trusted connection");
+ sqlx::query("DROP TRIGGER radroots_event_store_addressable_transition_update_guard")
+ .execute(&mut *connection)
+ .await
+ .expect("trusted transition guard removal");
+ sqlx::query("PRAGMA foreign_keys = OFF")
+ .execute(&mut *connection)
+ .await
+ .expect("disable trusted foreign-key enforcement");
+ sqlx::query("PRAGMA ignore_check_constraints = ON")
+ .execute(&mut *connection)
+ .await
+ .expect("enable trusted check-constraint bypass");
+ sqlx::query(
+ "UPDATE radroots_event_store_addressable_head_transition SET raw_head_event_seq = 0 WHERE transition_seq = 1",
+ )
+ .execute(&mut *connection)
+ .await
+ .expect("trusted prior-reference corruption");
+ sqlx::query("PRAGMA ignore_check_constraints = OFF")
+ .execute(&mut *connection)
+ .await
+ .expect("restore check-constraint enforcement");
+ sqlx::query("PRAGMA foreign_keys = ON")
+ .execute(&mut *connection)
+ .await
+ .expect("restore foreign-key enforcement");
+ drop(connection);
+
+ let error = store
+ .addressable_transition_page_v1(&scope, Some(&cursor), 64)
+ .await
+ .expect_err("corrupt prior reference must fail the resumed public feed");
+ assert!(matches!(
+ error,
+ RadrootsEventStoreError::AddressableTransitionCorruption { ref reason }
+ if reason.contains("prior_raw_head sequence is not positive")
+ ));
+ }
+
+ #[tokio::test]
async fn addressable_transition_feed_rejects_stored_event_authority_drift() {
for (label, store, guards, mutations, expected_reason) in [
(