lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

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:
Mcontracts/event_store_production_sources.toml | 12++++++------
Mcrates/event_store/src/model.rs | 3+--
Mcrates/event_store/src/model/addressable_transition_feed_v1.rs | 14++++++++++++++
Mcrates/event_store/src/model/current_visibility_v1.rs | 3+++
Mcrates/event_store/src/model/food_availability_projection_v1.rs | 14--------------
Mcrates/event_store/src/nip09/reconciliation_v1/visibility_oracle_v1.rs | 4++--
Mcrates/event_store/src/store.rs | 18++++++++++++++++++
Mcrates/event_store/src/store/addressable_transition_feed_v1.rs | 110+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mcrates/event_store/src/store/current_visibility_v1.rs | 137+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mcrates/event_store/src/store/protocol_storage_v1.rs | 16+++++++++++-----
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(&not_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(), });