commit 1a6d311cc2a51aad9d1b4fb74b841e729f6490d5
parent 69aaf27ba95fd97a2dd431f22331218a05ecc0c0
Author: triesap <tyson@radroots.org>
Date: Tue, 28 Jul 2026 12:02:39 +0000
event_store: make integrity comparisons aggregate
- remove redundant checks after typed food projection
- compare stored transition authority as complete tuples
- validate complete visibility and head snapshots
- cover short cursor and sequence boundaries
Diffstat:
10 files changed, 215 insertions(+), 116 deletions(-)
diff --git a/contracts/event_store_production_sources.toml b/contracts/event_store_production_sources.toml
@@ -15,7 +15,7 @@ sha256 = "5244eaf726eaa1f81973c98ded096e78b4874dedcd869f6ab6b844962efb51d6"
[[sources]]
path = "crates/event_store/src/model.rs"
-sha256 = "14a98fe4361baa90e74c499ca96cd47822531041dfd874281b1758796b8fd22e"
+sha256 = "dd56f9285e3b11248f3d9b28171d1a02861d1578a2ff607cbe593bd2c649b2a2"
[[sources]]
path = "crates/event_store/src/model/addressable_transition_feed_v1.rs"
@@ -27,7 +27,7 @@ sha256 = "f7ac71596075fe1bff6556d0eb0b63cc0fc0a20a8aca567f77fb05796f0c3d1c"
[[sources]]
path = "crates/event_store/src/model/food_availability_projection_v1.rs"
-sha256 = "ad6e31eb32bec50b61ac1810f6a62d9f693e2673e372ae12268160c2e59c6e6e"
+sha256 = "4403608130b68f5fb1398dd46aa14147e315fa101cb7c9ffc3f9ed1791413889"
[[sources]]
path = "crates/event_store/src/model/ingest_reconciliation_v1.rs"
@@ -55,7 +55,7 @@ sha256 = "30a465f5df9a37654d15df2a92ee445037ef41c6f5a5b31e216879f4903b3d6c"
[[sources]]
path = "crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs"
-sha256 = "b85b11baf75cc9d838c996c728d35141b7cd9d0134f5d5baae2159b7d93fa38b"
+sha256 = "9a9f3e12bd0eebf0e7ce570daa812cca0c3ffe1d0bd84a4a6f81b0d1452a4679"
[[sources]]
path = "crates/event_store/src/schema.rs"
@@ -71,11 +71,11 @@ sha256 = "ef277328d99ef75a978f2907dc654cbf391436b9db693c3be69655dccbe74f89"
[[sources]]
path = "crates/event_store/src/store/addressable_transition_feed_v1.rs"
-sha256 = "510f9cdeb3756b0f1ae4fcd5c85cb68ff5cb91344bc15cab0aa9c473938d2743"
+sha256 = "3903af04bd966ba6d19e2c2cd098478d269566676572d1385abdc23353962c91"
[[sources]]
path = "crates/event_store/src/store/current_visibility_v1.rs"
-sha256 = "ba4f77e758d98b25f034cbcc554dda833c839f3ccb8671c0b43997aaac764806"
+sha256 = "93129c02bf55858aa52e538ef50bf920d35719696183d339443e5c4060d2d4a3"
[[sources]]
path = "crates/event_store/src/store/food_availability_projection_v1.rs"
@@ -111,4 +111,4 @@ sha256 = "f9d6e28251c8ecaa60ec6ea1b4c2ab2bc0b9d125e67ea4849d35e09324a20108"
[[sources]]
path = "crates/event_store/src/store/protocol_storage_v1.rs"
-sha256 = "00378eb3b038e549fc4fa1da6797d99333be6baca2bce294be4b16a2c2bfa98e"
+sha256 = "e9a1041f7083962dc95c706d06da232abb1f0547b64351838ad6898f4209bdeb"
diff --git a/crates/event_store/src/model.rs b/crates/event_store/src/model.rs
@@ -251,8 +251,7 @@ impl RadrootsTransportObservation {
) -> Result<(), RadrootsEventStoreError> {
let target =
RadrootsTransportTarget::new(self.transport_kind.clone(), self.endpoint_uri.as_str())?;
- if target.uri() != &self.endpoint_uri || target.fingerprint() != &self.endpoint_fingerprint
- {
+ if target.fingerprint() != &self.endpoint_fingerprint {
return Err(
RadrootsEventStoreError::InvalidStoredTransportEndpointFingerprint {
event_id: event_id.to_owned(),
diff --git a/crates/event_store/src/model/addressable_transition_feed_v1.rs b/crates/event_store/src/model/addressable_transition_feed_v1.rs
@@ -626,6 +626,20 @@ mod tests {
)
));
+ value["source_generation"] = serde_json::json!("00");
+ assert!(matches!(
+ RadrootsAddressableTransitionCursorV1::from_json(
+ serde_json::to_string(&value)
+ .expect("short encoding JSON")
+ .as_str()
+ ),
+ Err(
+ RadrootsEventStoreError::AddressableTransitionCursorEncoding {
+ field: "source_generation"
+ }
+ )
+ ));
+
value["source_generation"] = serde_json::json!("42".repeat(32));
value["scope_fingerprint"] = serde_json::json!("AA".repeat(32));
assert!(matches!(
diff --git a/crates/event_store/src/model/current_visibility_v1.rs b/crates/event_store/src/model/current_visibility_v1.rs
@@ -206,6 +206,9 @@ mod tests {
assert!(
!evidence(Suppressed, DeletionRequestImmune, false, None).is_coherent_for_event(5, 10)
);
+ assert!(
+ !evidence(Visible, NoAuthorizedReference, false, None).is_coherent_for_event(5, 10)
+ );
assert!(!evidence(Visible, DeletionRequestImmune, true, None).is_coherent_for_event(5, 10));
assert!(
!evidence(Visible, DeletionRequestImmune, false, None).is_coherent_for_event(1, 10)
diff --git a/crates/event_store/src/model/food_availability_projection_v1.rs b/crates/event_store/src/model/food_availability_projection_v1.rs
@@ -271,20 +271,6 @@ impl RadrootsStoredFoodAvailabilityV1 {
.published_at()
.validate_created_at(created_at)
.map_err(|error| food_projection_drift(error.to_string()))?;
- if projection
- .quantity()
- .is_some_and(|quantity| quantity.unit() != projection.price().unit())
- {
- return Err(food_projection_drift(
- "quantity unit does not match the price unit",
- ));
- }
- if projection.images().len() > RADROOTS_FOOD_IMAGE_MAX_COUNT {
- return Err(food_projection_drift(format!(
- "bounded projection has {} images; maximum is {RADROOTS_FOOD_IMAGE_MAX_COUNT}",
- projection.images().len()
- )));
- }
let images = projection
.images()
diff --git a/crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs b/crates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs
@@ -280,8 +280,8 @@ impl<'a> OracleRequestIndexV1<'a> {
if replace {
evidence.authorized = Some(index);
}
- } else if evidence.unauthorized.is_none() {
- evidence.unauthorized = Some(index);
+ } else {
+ evidence.unauthorized.get_or_insert(index);
}
}
}
diff --git a/crates/event_store/src/store.rs b/crates/event_store/src/store.rs
@@ -7332,6 +7332,24 @@ CREATE TABLE aux.event_transport_observation (event_id TEXT);",
let author = RadrootsPublicKey::parse(event.pubkey_str()).expect("author");
let event_id = RadrootsEventId::parse(event.id_str()).expect("event id");
+ for (event_seq, source_transition_seq, expected_reason) in [
+ (0, 1, "event sequence must be positive"),
+ (1, 0, "source transition sequence must be positive"),
+ ] {
+ assert!(matches!(
+ crate::RadrootsStoredFoodAvailabilityV1::from_projection(
+ RadrootsEventStoreSourceGeneration::from_bytes([0x55; 32]),
+ author.clone(),
+ event_id.clone(),
+ event_seq,
+ 200,
+ source_transition_seq,
+ &projection,
+ ),
+ Err(RadrootsEventStoreError::FoodAvailabilityProjectionDrift { ref reason })
+ if reason.contains(expected_reason)
+ ));
+ }
assert!(matches!(
crate::RadrootsStoredFoodAvailabilityV1::from_projection(
RadrootsEventStoreSourceGeneration::from_bytes([0x55; 32]),
diff --git a/crates/event_store/src/store/addressable_transition_feed_v1.rs b/crates/event_store/src/store/addressable_transition_feed_v1.rs
@@ -173,9 +173,8 @@ async fn read_and_validate_source_authority(
let sealed_floor: i64 = row.try_get("sealed_floor_seq")?;
let sealed_high_water: i64 = row.try_get("sealed_last_transition_seq")?;
let sealed_count: i64 = row.try_get("sealed_transition_count")?;
- if sealed_floor != authority.floor
- || sealed_high_water != authority.high_water
- || sealed_count != expected_count
+ if (sealed_floor, sealed_high_water, sealed_count)
+ != (authority.floor, authority.high_water, expected_count)
{
return Err(RadrootsEventStoreError::AddressableTransitionSequenceGap {
reason: format!(
@@ -278,8 +277,10 @@ async fn transition_from_row(
.map_err(|error| corruption(error.to_string()))?;
let pubkey: String = row.try_get("pubkey")?;
let d_tag: String = row.try_get("d_tag")?;
- if !(30_000..=39_999).contains(&kind)
- || d_tag.len() > RADROOTS_ADDRESSABLE_TRANSITION_D_TAG_MAX_BYTES_V1
+ if (
+ (30_000..=39_999).contains(&kind),
+ d_tag.len() <= RADROOTS_ADDRESSABLE_TRANSITION_D_TAG_MAX_BYTES_V1,
+ ) != (true, true)
{
return Err(corruption("transition coordinate is outside wire bounds"));
}
@@ -354,11 +355,17 @@ async fn transition_from_row(
&raw_event,
)
.await?;
- if raw_event.created_at != raw_head_created_at
- || admission.status != admission_status
- || admission.code.as_deref() != admission_code.as_deref()
- || admission.contract.map(|contract| contract.id) != contract_id.as_deref()
- {
+ if (
+ raw_event.created_at,
+ admission.status,
+ admission.code.as_deref(),
+ admission.contract.map(|contract| contract.id),
+ ) != (
+ raw_head_created_at,
+ admission_status,
+ admission_code.as_deref(),
+ contract_id.as_deref(),
+ ) {
return Err(corruption(format!(
"transition {transition_seq} disagrees with its raw-head event"
)));
@@ -509,8 +516,8 @@ async fn validate_incremental_cause(
}
}
RadrootsAddressableTransitionRawHeadDecisionV1::NotHeadSelected => {
- if cause_event.kind != 5
- || cause_admission.status != RadrootsEventAdmissionStatus::Admitted
+ if (cause_event.kind, cause_admission.status)
+ != (5, RadrootsEventAdmissionStatus::Admitted)
{
return Err(corruption(
"non-head incremental transition was not caused by an admitted deletion request",
@@ -670,9 +677,11 @@ fn validate_transition_shape(
})
}
RadrootsAddressableTransitionVisibilityV1::NotAdmitted => {
- admission_status != RadrootsEventAdmissionStatus::Admitted
- && visible_event.is_none()
- && suppression.is_none()
+ (
+ admission_status == RadrootsEventAdmissionStatus::Admitted,
+ visible_event.is_none(),
+ suppression.is_none(),
+ ) == (false, true, true)
}
RadrootsAddressableTransitionVisibilityV1::Suppressed => {
admission_status == RadrootsEventAdmissionStatus::Admitted
@@ -728,8 +737,20 @@ fn suppression_evidence_from_transition_row(
.map(|value| u64_from_i64("transition.address_reference_cutoff", value))
.transpose()
.map_err(|error| corruption(error.to_string()))?;
- match (outcome, reason) {
- (Some(outcome), Some(reason)) => Ok(Some(RadrootsNip09SuppressionEvidenceV1 {
+ match (
+ outcome,
+ reason,
+ event_reference_request_id,
+ address_reference_request_id,
+ address_reference_cutoff,
+ ) {
+ (
+ Some(outcome),
+ Some(reason),
+ event_reference_request_id,
+ address_reference_request_id,
+ address_reference_cutoff,
+ ) => Ok(Some(RadrootsNip09SuppressionEvidenceV1 {
outcome: parse_suppression_outcome(outcome.as_str())
.map_err(|error| corruption(error.to_string()))?,
reason: parse_suppression_reason(reason.as_str())
@@ -738,13 +759,7 @@ fn suppression_evidence_from_transition_row(
address_reference_request_id,
address_reference_cutoff,
})),
- (None, None)
- if event_reference_request_id.is_none()
- && address_reference_request_id.is_none()
- && address_reference_cutoff.is_none() =>
- {
- Ok(None)
- }
+ (None, None, None, None, None) => Ok(None),
_ => Err(corruption("transition has incomplete suppression evidence")),
}
}
@@ -818,14 +833,23 @@ async fn load_and_validate_stored_event(
let event_pubkey = event.author().clone();
let tags_json = serde_json::to_string(&event.tags_as_vec())
.expect("an in-memory vector of string tags always serializes as JSON");
- if stored.event_id != event.id_str()
- || stored.pubkey != event.author_str()
- || stored.created_at != event.created_at_u64()
- || stored.kind != event.kind_u32()
- || stored.tags_json != tags_json
- || stored.content != event.content()
- || stored.sig != event.sig_str()
- {
+ if (
+ stored.event_id.as_str(),
+ stored.pubkey.as_str(),
+ stored.created_at,
+ stored.kind,
+ stored.tags_json.as_str(),
+ stored.content.as_str(),
+ stored.sig.as_str(),
+ ) != (
+ event.id_str(),
+ event.author_str(),
+ event.created_at_u64(),
+ event.kind_u32(),
+ tags_json.as_str(),
+ event.content(),
+ event.sig_str(),
+ ) {
return Err(corruption(format!(
"stored event `{}` disagrees with its signed raw JSON",
reference.event_id()
@@ -836,10 +860,15 @@ async fn load_and_validate_stored_event(
reconstructed.verified_event(),
)
.map_err(|error| corruption(format!("stored raw event cannot be admitted: {error}")))?;
- if admission.status != stored.admission_status
- || admission.contract.map(|contract| contract.id) != stored.contract_id.as_deref()
- || admission.valid_stream_eligible(event.kind_class()) != stored.valid_stream_eligible
- {
+ if (
+ admission.status,
+ admission.contract.map(|contract| contract.id),
+ admission.valid_stream_eligible(event.kind_class()),
+ ) != (
+ stored.admission_status,
+ stored.contract_id.as_deref(),
+ stored.valid_stream_eligible,
+ ) {
return Err(corruption(format!(
"stored event `{}` disagrees with registry-v7 admission",
reference.event_id()
@@ -855,9 +884,12 @@ async fn validate_addressable_reference(
reference: &RadrootsAddressableTransitionEventReferenceV1,
event: &RadrootsStoredRawEvent,
) -> Result<(), RadrootsEventStoreError> {
- if event.event_class != StoredEventClass::Addressable
- || event.kind != coordinate.kind()
- || event.pubkey != coordinate.pubkey().as_str()
+ if (event.event_class, event.kind, event.pubkey.as_str())
+ != (
+ StoredEventClass::Addressable,
+ coordinate.kind(),
+ coordinate.pubkey().as_str(),
+ )
{
return Err(corruption(format!(
"event `{}` does not match transition coordinate `{}:{}:{}`",
diff --git a/crates/event_store/src/store/current_visibility_v1.rs b/crates/event_store/src/store/current_visibility_v1.rs
@@ -209,31 +209,46 @@ fn validate_visibility_shape(
}
let valid = match visibility.decision {
RadrootsCurrentVisibilityDecisionV1::Visible => {
- visibility.event.admission_status
- == crate::model::RadrootsEventAdmissionStatus::Admitted
- && visibility.is_raw_head
- && evidence
- .is_some_and(|value| value.outcome == RadrootsNip09SuppressionOutcome::Visible)
+ (
+ visibility.event.admission_status,
+ visibility.is_raw_head,
+ evidence.map(|value| value.outcome),
+ ) == (
+ crate::model::RadrootsEventAdmissionStatus::Admitted,
+ true,
+ Some(RadrootsNip09SuppressionOutcome::Visible),
+ )
}
RadrootsCurrentVisibilityDecisionV1::NotAdmitted => {
- visibility.event.admission_status
- != crate::model::RadrootsEventAdmissionStatus::Admitted
- && evidence.is_none()
+ (
+ visibility.event.admission_status
+ == crate::model::RadrootsEventAdmissionStatus::Admitted,
+ evidence.is_none(),
+ ) == (false, true)
}
RadrootsCurrentVisibilityDecisionV1::NotCurrent => {
- visibility.event.admission_status
- == crate::model::RadrootsEventAdmissionStatus::Admitted
- && !visibility.is_raw_head
- && visibility.raw_head_event_id.is_some()
- && evidence.is_some()
+ (
+ visibility.event.admission_status,
+ visibility.is_raw_head,
+ visibility.raw_head_event_id.is_some(),
+ evidence.is_some(),
+ ) == (
+ crate::model::RadrootsEventAdmissionStatus::Admitted,
+ false,
+ true,
+ true,
+ )
}
RadrootsCurrentVisibilityDecisionV1::Suppressed => {
- visibility.event.admission_status
- == crate::model::RadrootsEventAdmissionStatus::Admitted
- && visibility.is_raw_head
- && evidence.is_some_and(|value| {
- value.outcome == RadrootsNip09SuppressionOutcome::Suppressed
- })
+ (
+ visibility.event.admission_status,
+ visibility.is_raw_head,
+ evidence.map(|value| value.outcome),
+ ) == (
+ crate::model::RadrootsEventAdmissionStatus::Admitted,
+ true,
+ Some(RadrootsNip09SuppressionOutcome::Suppressed),
+ )
}
};
if !valid {
@@ -289,35 +304,50 @@ async fn validate_addressable_head_projection(
})
.transpose()
.map_err(|error| visibility_authority_error("stored address deletion cutoff", error))?;
- if row.try_get::<String, _>("raw_head_event_id")? != visibility.event.event_id
- || row.try_get::<i64, _>("raw_head_event_seq")? != visibility.event.seq
- || stored_created_at != visibility.event.created_at
- || row.try_get::<String, _>("admission_status")?
- != visibility.event.admission_status.as_str()
- || row.try_get::<Option<String>, _>("admission_code")?
- != row.try_get::<Option<String>, _>("coordinate_admission_code")?
- || row.try_get::<Option<String>, _>("contract_id")? != visibility.event.contract_id
- || row.try_get::<String, _>("visibility")? != visibility.decision.as_str()
- || row
- .try_get::<Option<String>, _>("nip09_outcome")?
- .as_deref()
- != evidence.map(|value| value.outcome.code())
- || row.try_get::<Option<String>, _>("nip09_reason")?.as_deref()
- != evidence.map(|value| value.reason.code())
- || row
- .try_get::<Option<String>, _>("event_reference_request_id")?
- .as_deref()
- != evidence
- .and_then(|value| value.event_reference_request_id.as_ref())
- .map(RadrootsEventId::as_str)
- || row
- .try_get::<Option<String>, _>("address_reference_request_id")?
- .as_deref()
- != evidence
- .and_then(|value| value.address_reference_request_id.as_ref())
- .map(RadrootsEventId::as_str)
- || stored_cutoff != evidence.and_then(|value| value.address_reference_cutoff)
- {
+ let stored_raw_head_event_id: String = row.try_get("raw_head_event_id")?;
+ let stored_raw_head_event_seq: i64 = row.try_get("raw_head_event_seq")?;
+ let stored_admission_status: String = row.try_get("admission_status")?;
+ let stored_admission_code: Option<String> = row.try_get("admission_code")?;
+ let coordinate_admission_code: Option<String> = row.try_get("coordinate_admission_code")?;
+ let stored_contract_id: Option<String> = row.try_get("contract_id")?;
+ let stored_visibility: String = row.try_get("visibility")?;
+ let stored_outcome: Option<String> = row.try_get("nip09_outcome")?;
+ let stored_reason: Option<String> = row.try_get("nip09_reason")?;
+ let stored_event_reference_request_id: Option<String> =
+ row.try_get("event_reference_request_id")?;
+ let stored_address_reference_request_id: Option<String> =
+ row.try_get("address_reference_request_id")?;
+ if (
+ stored_raw_head_event_id.as_str(),
+ stored_raw_head_event_seq,
+ stored_created_at,
+ stored_admission_status.as_str(),
+ stored_admission_code.as_deref(),
+ stored_contract_id.as_deref(),
+ stored_visibility.as_str(),
+ stored_outcome.as_deref(),
+ stored_reason.as_deref(),
+ stored_event_reference_request_id.as_deref(),
+ stored_address_reference_request_id.as_deref(),
+ stored_cutoff,
+ ) != (
+ visibility.event.event_id.as_str(),
+ visibility.event.seq,
+ visibility.event.created_at,
+ visibility.event.admission_status.as_str(),
+ coordinate_admission_code.as_deref(),
+ visibility.event.contract_id.as_deref(),
+ visibility.decision.as_str(),
+ evidence.map(|value| value.outcome.code()),
+ evidence.map(|value| value.reason.code()),
+ evidence
+ .and_then(|value| value.event_reference_request_id.as_ref())
+ .map(RadrootsEventId::as_str),
+ evidence
+ .and_then(|value| value.address_reference_request_id.as_ref())
+ .map(RadrootsEventId::as_str),
+ evidence.and_then(|value| value.address_reference_cutoff),
+ ) {
return current_visibility_drift(format!(
"central visibility disagrees with addressable head state for `{}`",
visibility.event.event_id
@@ -702,6 +732,17 @@ mod tests {
not_admitted.event.contract_id = None;
not_admitted.event.valid_stream_eligible = false;
validate_visibility_shape(¬_admitted).expect("not admitted");
+ let mut nonregular_not_admitted = visibility(
+ StoredEventClass::Replaceable,
+ RadrootsCurrentVisibilityDecisionV1::NotAdmitted,
+ false,
+ None,
+ None,
+ );
+ nonregular_not_admitted.event.admission_status = RadrootsEventAdmissionStatus::Unsupported;
+ nonregular_not_admitted.event.contract_id = None;
+ nonregular_not_admitted.event.valid_stream_eligible = false;
+ validate_visibility_shape(&nonregular_not_admitted).expect("nonregular not admitted");
validate_visibility_shape(&visibility(
StoredEventClass::Replaceable,
diff --git a/crates/event_store/src/store/protocol_storage_v1.rs b/crates/event_store/src/store/protocol_storage_v1.rs
@@ -218,11 +218,17 @@ fn validate_raw_head_snapshot(
});
}
};
- if &stored_coordinate != requested_coordinate
- || stored_coordinate != expected_coordinate
- || raw_head.event_id != raw_event.event_id
- || raw_head.created_at != raw_event.created_at
- {
+ if (
+ &stored_coordinate,
+ &stored_coordinate,
+ raw_head.event_id.as_str(),
+ raw_head.created_at,
+ ) != (
+ requested_coordinate,
+ &expected_coordinate,
+ raw_event.event_id.as_str(),
+ raw_event.created_at,
+ ) {
return Err(RadrootsEventStoreError::StoredHeadInconsistent {
event_id: raw_head.event_id.clone(),
});