0.3.16-pre.007
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/persistence.rs
|
||||
// version: 4
|
||||
// version: 5
|
||||
|
||||
/// Canonical entity disposition produced by one Worker Store persistence attempt.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
@@ -28,6 +28,7 @@ pub(crate) enum RawTransactionIngestObservationPersistence {
|
||||
pub(crate) struct RawTransactionIngestPersistenceOutcome {
|
||||
entity: crate::RawTransactionIngestEntityPersistence,
|
||||
observation: crate::RawTransactionIngestObservationPersistence,
|
||||
quarantined_conflict: bool,
|
||||
}
|
||||
|
||||
impl crate::RawTransactionIngestPersistenceOutcome {
|
||||
@@ -42,6 +43,12 @@ impl crate::RawTransactionIngestPersistenceOutcome {
|
||||
pub(crate) const fn observation(self) -> crate::RawTransactionIngestObservationPersistence {
|
||||
return self.observation;
|
||||
}
|
||||
|
||||
/// Reports whether Store durably quarantined divergent or incomparable content without failing the acquisition.
|
||||
#[must_use]
|
||||
pub(crate) const fn quarantined_conflict(self) -> bool {
|
||||
return self.quarantined_conflict;
|
||||
}
|
||||
}
|
||||
|
||||
/// Private backend-neutral Store persistence port used by the Worker and deterministic tests.
|
||||
@@ -56,12 +63,6 @@ pub(crate) trait RawTransactionIngestPersistencePort: std::marker::Send + std::m
|
||||
observation: ksp_store_lib::RawTransactionObservation,
|
||||
mode: ksp_store_lib::RawTransactionAcquisitionMode,
|
||||
) -> ksp_store_lib::StoreApiFuture<'a, ksp_store_lib::Result<ksp_store_lib::RawAcquisitionWriteOutcome>>;
|
||||
|
||||
/// Records one additional observation for an already durable canonical RAW transaction.
|
||||
fn record_observation<'a>(
|
||||
&'a self,
|
||||
observation: ksp_store_lib::RawTransactionObservation,
|
||||
) -> ksp_store_lib::StoreApiFuture<'a, ksp_store_lib::Result<ksp_store_lib::RawObservationWriteOutcome>>;
|
||||
}
|
||||
|
||||
impl crate::RawTransactionIngestPersistencePort for ksp_store_lib::Store {
|
||||
@@ -77,13 +78,6 @@ impl crate::RawTransactionIngestPersistencePort for ksp_store_lib::Store {
|
||||
) -> ksp_store_lib::StoreApiFuture<'a, ksp_store_lib::Result<ksp_store_lib::RawAcquisitionWriteOutcome>> {
|
||||
return ksp_store_lib::RawTransactionWrite::persist_raw_transaction_acquisition(self, transaction, observation, mode);
|
||||
}
|
||||
|
||||
fn record_observation<'a>(
|
||||
&'a self,
|
||||
observation: ksp_store_lib::RawTransactionObservation,
|
||||
) -> ksp_store_lib::StoreApiFuture<'a, ksp_store_lib::Result<ksp_store_lib::RawObservationWriteOutcome>> {
|
||||
return ksp_store_lib::RawTransactionObservationWrite::record_raw_transaction_observation(self, observation);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, Ord, PartialEq, PartialOrd)]
|
||||
@@ -92,24 +86,10 @@ struct RawTransactionIngestPersistenceKey {
|
||||
signature: ksp_store_lib::RawTransactionSignature,
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
struct RawTransactionIngestCanonicalState {
|
||||
block_time: std::option::Option<ksp_store_lib::RawTimestamp>,
|
||||
content_hash: ksp_store_lib::RawContentHash,
|
||||
format_id: ksp_store_lib::RawFormatId,
|
||||
format_version: u32,
|
||||
slot: u64,
|
||||
}
|
||||
|
||||
/// Private bounded run-local cache serializing repeated canonical identities before Store writes.
|
||||
pub(crate) struct RawTransactionIngestPersistenceConvergence {
|
||||
max_entries: usize,
|
||||
entries: std::sync::Mutex<
|
||||
std::collections::BTreeMap<
|
||||
RawTransactionIngestPersistenceKey,
|
||||
std::sync::Arc<tokio::sync::Mutex<std::option::Option<RawTransactionIngestCanonicalState>>>,
|
||||
>,
|
||||
>,
|
||||
entries: std::sync::Mutex<std::collections::BTreeMap<RawTransactionIngestPersistenceKey, std::sync::Arc<tokio::sync::Mutex<()>>>>,
|
||||
}
|
||||
|
||||
impl crate::RawTransactionIngestPersistenceConvergence {
|
||||
@@ -119,10 +99,7 @@ impl crate::RawTransactionIngestPersistenceConvergence {
|
||||
return Self { max_entries, entries: std::sync::Mutex::new(std::collections::BTreeMap::new()) };
|
||||
}
|
||||
|
||||
fn entry(
|
||||
&self,
|
||||
key: RawTransactionIngestPersistenceKey,
|
||||
) -> std::option::Option<std::sync::Arc<tokio::sync::Mutex<std::option::Option<RawTransactionIngestCanonicalState>>>> {
|
||||
fn entry(&self, key: RawTransactionIngestPersistenceKey) -> std::option::Option<std::sync::Arc<tokio::sync::Mutex<()>>> {
|
||||
let mut entries = match self.entries.lock() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(poisoned) => poisoned.into_inner(),
|
||||
@@ -144,7 +121,7 @@ impl crate::RawTransactionIngestPersistenceConvergence {
|
||||
if entries.len() >= self.max_entries {
|
||||
return std::option::Option::None;
|
||||
}
|
||||
let entry = std::sync::Arc::new(tokio::sync::Mutex::new(std::option::Option::None));
|
||||
let entry = std::sync::Arc::new(tokio::sync::Mutex::new(()));
|
||||
entries.insert(key, std::sync::Arc::clone(&entry));
|
||||
return std::option::Option::Some(entry);
|
||||
}
|
||||
@@ -189,44 +166,14 @@ where
|
||||
if acquisition.observation().transaction() != &reference {
|
||||
return std::result::Result::Err(crate::runtime_error("persistence.acquisition_reference_mismatch"));
|
||||
}
|
||||
let canonical_state = canonical_state(acquisition.transaction());
|
||||
let key = RawTransactionIngestPersistenceKey { network: reference.network().clone(), signature: reference.signature() };
|
||||
let entry = convergence.entry(key);
|
||||
let entry = match entry {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return crate::persist_raw_transaction_ingest_acquisition(port, acquisition).await,
|
||||
};
|
||||
let mut known_state = entry.lock().await;
|
||||
if let std::option::Option::Some(known_state_value) = known_state.as_ref() {
|
||||
if known_state_value != &canonical_state {
|
||||
return std::result::Result::Err(crate::content_conflict_error());
|
||||
}
|
||||
let (_transaction, observation) = acquisition.into_parts();
|
||||
let result = port.record_observation(observation).await;
|
||||
return match result {
|
||||
std::result::Result::Ok(outcome) => map_additional_observation_outcome(outcome),
|
||||
std::result::Result::Err(error) => map_store_error(error),
|
||||
};
|
||||
}
|
||||
let outcome = crate::persist_raw_transaction_ingest_acquisition(port, acquisition).await;
|
||||
let outcome = match outcome {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if outcome.entity() != crate::RawTransactionIngestEntityPersistence::SkippedPurged {
|
||||
*known_state = std::option::Option::Some(canonical_state);
|
||||
}
|
||||
return std::result::Result::Ok(outcome);
|
||||
}
|
||||
|
||||
fn canonical_state(transaction: &ksp_store_lib::RawTransaction) -> RawTransactionIngestCanonicalState {
|
||||
return RawTransactionIngestCanonicalState {
|
||||
block_time: transaction.block_time(),
|
||||
content_hash: transaction.payload().content_hash(),
|
||||
format_id: transaction.payload().format_id().clone(),
|
||||
format_version: transaction.payload().format_version(),
|
||||
slot: transaction.slot(),
|
||||
};
|
||||
let _identity_guard = entry.lock().await;
|
||||
return crate::persist_raw_transaction_ingest_acquisition(port, acquisition).await;
|
||||
}
|
||||
|
||||
fn map_store_error(error: ksp_core_lib::Error) -> ksp_core_lib::Result<crate::RawTransactionIngestPersistenceOutcome> {
|
||||
@@ -239,49 +186,74 @@ fn map_store_error(error: ksp_core_lib::Error) -> ksp_core_lib::Result<crate::Ra
|
||||
fn map_store_outcome(outcome: ksp_store_lib::RawAcquisitionWriteOutcome) -> ksp_core_lib::Result<crate::RawTransactionIngestPersistenceOutcome> {
|
||||
let entity = outcome.entity();
|
||||
let observation = outcome.observation();
|
||||
let quarantined_conflict = match validate_transaction_variant_outcome(outcome.transaction_variant(), entity, observation) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if entity == ksp_store_lib::RawEntityWriteOutcome::Inserted && observation == ksp_store_lib::RawObservationWriteOutcome::Inserted {
|
||||
return std::result::Result::Ok(crate::RawTransactionIngestPersistenceOutcome {
|
||||
entity: crate::RawTransactionIngestEntityPersistence::Inserted,
|
||||
observation: crate::RawTransactionIngestObservationPersistence::Inserted,
|
||||
quarantined_conflict,
|
||||
});
|
||||
}
|
||||
if entity == ksp_store_lib::RawEntityWriteOutcome::AlreadyPresent && observation == ksp_store_lib::RawObservationWriteOutcome::Inserted {
|
||||
return std::result::Result::Ok(crate::RawTransactionIngestPersistenceOutcome {
|
||||
entity: crate::RawTransactionIngestEntityPersistence::AlreadyPresent,
|
||||
observation: crate::RawTransactionIngestObservationPersistence::Inserted,
|
||||
quarantined_conflict,
|
||||
});
|
||||
}
|
||||
if entity == ksp_store_lib::RawEntityWriteOutcome::AlreadyPresent && observation == ksp_store_lib::RawObservationWriteOutcome::AlreadyPresent {
|
||||
return std::result::Result::Ok(crate::RawTransactionIngestPersistenceOutcome {
|
||||
entity: crate::RawTransactionIngestEntityPersistence::AlreadyPresent,
|
||||
observation: crate::RawTransactionIngestObservationPersistence::AlreadyPresent,
|
||||
quarantined_conflict,
|
||||
});
|
||||
}
|
||||
if entity == ksp_store_lib::RawEntityWriteOutcome::SkippedPurged && observation == ksp_store_lib::RawObservationWriteOutcome::NotRecorded {
|
||||
return std::result::Result::Ok(crate::RawTransactionIngestPersistenceOutcome {
|
||||
entity: crate::RawTransactionIngestEntityPersistence::SkippedPurged,
|
||||
observation: crate::RawTransactionIngestObservationPersistence::NotRecorded,
|
||||
quarantined_conflict,
|
||||
});
|
||||
}
|
||||
return std::result::Result::Err(crate::runtime_error("persistence.store_outcome_invalid"));
|
||||
}
|
||||
|
||||
fn map_additional_observation_outcome(
|
||||
outcome: ksp_store_lib::RawObservationWriteOutcome,
|
||||
) -> ksp_core_lib::Result<crate::RawTransactionIngestPersistenceOutcome> {
|
||||
return match outcome {
|
||||
ksp_store_lib::RawObservationWriteOutcome::Inserted => std::result::Result::Ok(crate::RawTransactionIngestPersistenceOutcome {
|
||||
entity: crate::RawTransactionIngestEntityPersistence::AlreadyPresent,
|
||||
observation: crate::RawTransactionIngestObservationPersistence::Inserted,
|
||||
}),
|
||||
ksp_store_lib::RawObservationWriteOutcome::AlreadyPresent => std::result::Result::Ok(crate::RawTransactionIngestPersistenceOutcome {
|
||||
entity: crate::RawTransactionIngestEntityPersistence::AlreadyPresent,
|
||||
observation: crate::RawTransactionIngestObservationPersistence::AlreadyPresent,
|
||||
}),
|
||||
ksp_store_lib::RawObservationWriteOutcome::NotRecorded => {
|
||||
std::result::Result::Err(crate::runtime_error("persistence.additional_observation_not_recorded"))
|
||||
fn validate_transaction_variant_outcome(
|
||||
transaction_variant: std::option::Option<ksp_store_lib::RawTransactionVariantWriteOutcome>,
|
||||
entity: ksp_store_lib::RawEntityWriteOutcome,
|
||||
observation: ksp_store_lib::RawObservationWriteOutcome,
|
||||
) -> ksp_core_lib::Result<bool> {
|
||||
let recorded_observation =
|
||||
matches!(observation, ksp_store_lib::RawObservationWriteOutcome::Inserted | ksp_store_lib::RawObservationWriteOutcome::AlreadyPresent);
|
||||
return match transaction_variant {
|
||||
std::option::Option::None => std::result::Result::Ok(false),
|
||||
std::option::Option::Some(ksp_store_lib::RawTransactionVariantWriteOutcome::InsertedCanonical)
|
||||
if entity == ksp_store_lib::RawEntityWriteOutcome::Inserted && observation == ksp_store_lib::RawObservationWriteOutcome::Inserted =>
|
||||
{
|
||||
std::result::Result::Ok(false)
|
||||
},
|
||||
_ => std::result::Result::Err(crate::runtime_error("persistence.additional_observation_outcome_invalid")),
|
||||
std::option::Option::Some(
|
||||
ksp_store_lib::RawTransactionVariantWriteOutcome::ObservedExact
|
||||
| ksp_store_lib::RawTransactionVariantWriteOutcome::ObservedCompatibleLessComplete
|
||||
| ksp_store_lib::RawTransactionVariantWriteOutcome::PromotedCompatibleMoreComplete,
|
||||
) if entity == ksp_store_lib::RawEntityWriteOutcome::AlreadyPresent && recorded_observation => std::result::Result::Ok(false),
|
||||
std::option::Option::Some(ksp_store_lib::RawTransactionVariantWriteOutcome::QuarantinedConflict)
|
||||
if entity == ksp_store_lib::RawEntityWriteOutcome::AlreadyPresent && recorded_observation =>
|
||||
{
|
||||
std::result::Result::Ok(true)
|
||||
},
|
||||
std::option::Option::Some(ksp_store_lib::RawTransactionVariantWriteOutcome::SkippedPurged)
|
||||
if entity == ksp_store_lib::RawEntityWriteOutcome::SkippedPurged && observation == ksp_store_lib::RawObservationWriteOutcome::NotRecorded =>
|
||||
{
|
||||
std::result::Result::Ok(false)
|
||||
},
|
||||
std::option::Option::Some(ksp_store_lib::RawTransactionVariantWriteOutcome::Rehydrated) => {
|
||||
std::result::Result::Err(crate::runtime_error("persistence.store_variant_rehydrated_in_normal_mode"))
|
||||
},
|
||||
_ => std::result::Result::Err(crate::runtime_error("persistence.store_variant_outcome_invalid")),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/snapshot.rs
|
||||
// version: 10
|
||||
// version: 11
|
||||
|
||||
/// Runtime-neutral boxed future resolving to one newer concrete RAW transaction ingest Worker snapshot.
|
||||
pub type RawTransactionIngestSnapshotFuture<'a> =
|
||||
@@ -1132,6 +1132,13 @@ impl crate::RawTransactionIngestSnapshotPublisher {
|
||||
let mut entity_skipped_purged_total = self.snapshot.entity_skipped_purged_total;
|
||||
let mut observation_inserted_total = self.snapshot.observation_inserted_total;
|
||||
let mut observation_already_present_total = self.snapshot.observation_already_present_total;
|
||||
let mut content_conflict_total = self.snapshot.content_conflict_total;
|
||||
if outcome.quarantined_conflict() {
|
||||
content_conflict_total = match checked_counter(content_conflict_total, "content_conflict_total") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
}
|
||||
match outcome.entity() {
|
||||
crate::RawTransactionIngestEntityPersistence::Inserted => {
|
||||
entity_inserted_total = match checked_counter(entity_inserted_total, "entity_inserted_total") {
|
||||
@@ -1173,6 +1180,7 @@ impl crate::RawTransactionIngestSnapshotPublisher {
|
||||
self.snapshot.entity_skipped_purged_total = entity_skipped_purged_total;
|
||||
self.snapshot.observation_inserted_total = observation_inserted_total;
|
||||
self.snapshot.observation_already_present_total = observation_already_present_total;
|
||||
self.snapshot.content_conflict_total = content_conflict_total;
|
||||
return self.publish(state, admission_queue_depth, in_flight_persistence);
|
||||
}
|
||||
|
||||
@@ -1279,6 +1287,9 @@ fn health_for_state(
|
||||
return ksp_worker_api::WorkerHealth::Unhealthy;
|
||||
}
|
||||
if source_total > 0 && snapshot.source_active == snapshot.source_total {
|
||||
if snapshot.content_conflict_total > 0 {
|
||||
return ksp_worker_api::WorkerHealth::Degraded;
|
||||
}
|
||||
return ksp_worker_api::WorkerHealth::Healthy;
|
||||
}
|
||||
if source_total > 0
|
||||
@@ -1297,6 +1308,7 @@ fn health_for_state(
|
||||
ksp_worker_api::WorkerState::Running if snapshot.source_total > 0 && snapshot.source_active < snapshot.source_total => {
|
||||
ksp_worker_api::WorkerHealth::Degraded
|
||||
},
|
||||
ksp_worker_api::WorkerState::Running if snapshot.content_conflict_total > 0 => ksp_worker_api::WorkerHealth::Degraded,
|
||||
ksp_worker_api::WorkerState::Running => ksp_worker_api::WorkerHealth::Healthy,
|
||||
ksp_worker_api::WorkerState::Stopping | ksp_worker_api::WorkerState::Stopped => previous,
|
||||
ksp_worker_api::WorkerState::Faulted(_) => ksp_worker_api::WorkerHealth::Unhealthy,
|
||||
|
||||
Reference in New Issue
Block a user