0.3.16-pre.008
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-postgres-lib/tests/postgres_raw_transaction_live.rs
|
||||
// version: 9
|
||||
// version: 10
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -12,6 +12,68 @@
|
||||
//! drops only the isolated schema it proved absent before the run.
|
||||
|
||||
const LIVE_CANCEL_WAIT: std::time::Duration = std::time::Duration::from_millis(300);
|
||||
const LIVE_CONFLICT_STATE_SQL: &str = r#"SELECT
|
||||
(SELECT COUNT(*) = 2 FROM ksp_raw_transaction_variants WHERE transaction_signature = $1)
|
||||
AND (SELECT COUNT(*) = 1 FROM ksp_raw_transaction_canonical_selectors WHERE transaction_signature = $1)
|
||||
AND (SELECT COUNT(*) = 2 FROM ksp_raw_transaction_observations WHERE transaction_signature = $1)
|
||||
AND (SELECT COUNT(*) = 2 FROM ksp_raw_transaction_observation_variants WHERE transaction_signature = $1)
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM ksp_raw_transaction_conflicts AS conflict
|
||||
INNER JOIN ksp_raw_transaction_canonical_selectors AS selector
|
||||
ON selector.transaction_signature = conflict.transaction_signature
|
||||
WHERE conflict.transaction_signature = $1
|
||||
AND conflict.status = 'open'
|
||||
AND conflict.revision = 1
|
||||
AND conflict.canonical_variant_id = selector.canonical_variant_id
|
||||
AND conflict.incoming_variant_id <> conflict.canonical_variant_id
|
||||
)
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM ksp_raw_transactions AS canonical
|
||||
INNER JOIN ksp_raw_transaction_canonical_selectors AS selector
|
||||
ON selector.transaction_signature = canonical.signature
|
||||
INNER JOIN ksp_raw_transaction_variants AS variant
|
||||
ON variant.transaction_signature = selector.transaction_signature
|
||||
AND variant.variant_id = selector.canonical_variant_id
|
||||
WHERE canonical.signature = $1
|
||||
AND variant.slot = canonical.slot
|
||||
AND variant.block_time_unix_millis IS NOT DISTINCT FROM canonical.block_time_unix_millis
|
||||
AND variant.format_id = canonical.format_id
|
||||
AND variant.format_version = canonical.format_version
|
||||
AND variant.content_hash = canonical.content_hash
|
||||
AND variant.payload = canonical.payload
|
||||
)
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM ksp_raw_transaction_observation_variants
|
||||
WHERE observation_key = $2 AND transaction_signature = $1
|
||||
)
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM ksp_raw_transaction_observation_variants
|
||||
WHERE observation_key = $3 AND transaction_signature = $1
|
||||
)"#;
|
||||
const LIVE_CONFLICT_ROLLBACK_STATE_SQL: &str = r#"SELECT
|
||||
(SELECT COUNT(*) = 1 FROM ksp_raw_transaction_variants WHERE transaction_signature = $1)
|
||||
AND (SELECT COUNT(*) = 1 FROM ksp_raw_transaction_canonical_selectors WHERE transaction_signature = $1)
|
||||
AND (SELECT COUNT(*) = 0 FROM ksp_raw_transaction_conflicts WHERE transaction_signature = $1)
|
||||
AND (SELECT COUNT(*) = 1 FROM ksp_raw_transaction_observations WHERE transaction_signature = $1)
|
||||
AND (SELECT COUNT(*) = 1 FROM ksp_raw_transaction_observation_variants WHERE transaction_signature = $1)
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM ksp_raw_transactions AS canonical
|
||||
INNER JOIN ksp_raw_transaction_canonical_selectors AS selector
|
||||
ON selector.transaction_signature = canonical.signature
|
||||
INNER JOIN ksp_raw_transaction_variants AS variant
|
||||
ON variant.transaction_signature = selector.transaction_signature
|
||||
AND variant.variant_id = selector.canonical_variant_id
|
||||
WHERE canonical.signature = $1
|
||||
AND variant.slot = canonical.slot
|
||||
AND variant.block_time_unix_millis IS NOT DISTINCT FROM canonical.block_time_unix_millis
|
||||
AND variant.format_id = canonical.format_id
|
||||
AND variant.format_version = canonical.format_version
|
||||
AND variant.content_hash = canonical.content_hash
|
||||
AND variant.payload = canonical.payload
|
||||
)"#;
|
||||
const LIVE_INDEX_EXISTS_SQL: &str = r#"SELECT EXISTS (
|
||||
SELECT 1 FROM pg_indexes
|
||||
WHERE schemaname = current_schema()
|
||||
@@ -214,6 +276,10 @@ async fn run_raw_transaction_scenario(admin: &mut tokio_postgres::Client, uri: &
|
||||
if let std::result::Result::Err(error) = divergent_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let conflict_rollback_result = prove_conflict_case_rollback(admin, uri).await;
|
||||
if let std::result::Result::Err(error) = conflict_rollback_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let race_result = prove_retention_races(uri).await;
|
||||
if let std::result::Result::Err(error) = race_result {
|
||||
return std::result::Result::Err(error);
|
||||
@@ -414,23 +480,61 @@ async fn prove_concurrent_divergent_insert(admin: &tokio_postgres::Client, uri:
|
||||
};
|
||||
let pair = [first_result, second_result];
|
||||
let inserted = pair.iter().filter(|value| return matches_inserted(value)).count();
|
||||
let conflicts = pair
|
||||
.iter()
|
||||
.filter(|value| matches!(value, LivePersistResult::BackendError(ksp_store_postgres_lib::PostgresBackendErrorKind::Conflict)))
|
||||
.count();
|
||||
if inserted != 1 || conflicts != 1 {
|
||||
let quarantined = pair.iter().filter(|value| return matches_quarantined_conflict(value)).count();
|
||||
if inserted != 1 || quarantined != 1 {
|
||||
return std::result::Result::Err(LiveFailure::new("concurrent_divergent_outcome"));
|
||||
}
|
||||
let reference = match raw_reference(30) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let first_key = ksp_store_api::RawObservationKey::new([31; 32]);
|
||||
let second_key = ksp_store_api::RawObservationKey::new([32; 32]);
|
||||
let first_exists = observation_exists(admin, &first_key).await;
|
||||
let second_exists = observation_exists(admin, &second_key).await;
|
||||
let count = match (first_exists, second_exists) {
|
||||
(std::result::Result::Ok(first_value), std::result::Result::Ok(second_value)) => usize::from(first_value) + usize::from(second_value),
|
||||
_ => return std::result::Result::Err(LiveFailure::new("concurrent_divergent_observation_probe")),
|
||||
let state = conflict_state_is_exact(admin, &reference, &first_key, &second_key).await;
|
||||
match state {
|
||||
std::result::Result::Ok(true) => {},
|
||||
std::result::Result::Ok(false) => return std::result::Result::Err(LiveFailure::new("concurrent_divergent_state")),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
async fn prove_conflict_case_rollback(admin: &tokio_postgres::Client, uri: &str) -> std::result::Result<(), LiveFailure> {
|
||||
let seed = persist_once(uri.to_owned(), 82, 8_200, 82, 82).await;
|
||||
match seed {
|
||||
std::result::Result::Ok(LivePersistResult::Outcome(value)) if value.entity() == ksp_store_api::RawEntityWriteOutcome::Inserted => {},
|
||||
_ => return std::result::Result::Err(LiveFailure::new("conflict_rollback_seed")),
|
||||
}
|
||||
let backend = match open_backend(uri, "devnet", true, true).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if count != 1 {
|
||||
return std::result::Result::Err(LiveFailure::new("concurrent_divergent_rollback"));
|
||||
let transaction = match raw_transaction(82, 8_200, 83) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let observation = match raw_observation(10, 82, 83) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let result = backend.persist_raw_transaction_acquisition(transaction, observation, ksp_store_api::RawTransactionAcquisitionMode::Normal).await;
|
||||
match result {
|
||||
std::result::Result::Err(error) if error.kind() == ksp_store_postgres_lib::PostgresBackendErrorKind::Conflict => {},
|
||||
_ => return std::result::Result::Err(LiveFailure::new("conflict_rollback_expected_failure")),
|
||||
}
|
||||
let close_result = close_backend(backend).await;
|
||||
if let std::result::Result::Err(error) = close_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let reference = match raw_reference(82) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let state = conflict_rollback_state_is_exact(admin, &reference).await;
|
||||
match state {
|
||||
std::result::Result::Ok(true) => {},
|
||||
std::result::Result::Ok(false) => return std::result::Result::Err(LiveFailure::new("conflict_rollback_state")),
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
@@ -971,6 +1075,16 @@ fn matches_inserted(value: &LivePersistResult) -> bool {
|
||||
);
|
||||
}
|
||||
|
||||
fn matches_quarantined_conflict(value: &LivePersistResult) -> bool {
|
||||
return matches!(
|
||||
value,
|
||||
LivePersistResult::Outcome(outcome)
|
||||
if outcome.entity() == ksp_store_api::RawEntityWriteOutcome::AlreadyPresent
|
||||
&& outcome.observation() == ksp_store_api::RawObservationWriteOutcome::Inserted
|
||||
&& outcome.transaction_variant() == std::option::Option::Some(ksp_store_api::RawTransactionVariantWriteOutcome::QuarantinedConflict)
|
||||
);
|
||||
}
|
||||
|
||||
fn matches_already_present(value: &LivePersistResult) -> bool {
|
||||
return matches!(
|
||||
value,
|
||||
@@ -1277,6 +1391,42 @@ async fn observation_exists(client: &tokio_postgres::Client, key: &ksp_store_api
|
||||
};
|
||||
}
|
||||
|
||||
async fn conflict_state_is_exact(
|
||||
client: &tokio_postgres::Client,
|
||||
reference: &ksp_store_api::RawTransactionReference,
|
||||
first_key: &ksp_store_api::RawObservationKey,
|
||||
second_key: &ksp_store_api::RawObservationKey,
|
||||
) -> std::result::Result<bool, LiveFailure> {
|
||||
let signature = reference.signature();
|
||||
let signature_bytes: &[u8] = signature.as_bytes();
|
||||
let first_key_bytes: &[u8] = first_key.as_bytes();
|
||||
let second_key_bytes: &[u8] = second_key.as_bytes();
|
||||
let row = match client.query_one(LIVE_CONFLICT_STATE_SQL, &[&signature_bytes, &first_key_bytes, &second_key_bytes]).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(LiveFailure::new("conflict_state_probe")),
|
||||
};
|
||||
return match row.try_get::<usize, bool>(0) {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(_) => std::result::Result::Err(LiveFailure::new("conflict_state_decode")),
|
||||
};
|
||||
}
|
||||
|
||||
async fn conflict_rollback_state_is_exact(
|
||||
client: &tokio_postgres::Client,
|
||||
reference: &ksp_store_api::RawTransactionReference,
|
||||
) -> std::result::Result<bool, LiveFailure> {
|
||||
let signature = reference.signature();
|
||||
let signature_bytes: &[u8] = signature.as_bytes();
|
||||
let row = match client.query_one(LIVE_CONFLICT_ROLLBACK_STATE_SQL, &[&signature_bytes]).await {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(LiveFailure::new("conflict_rollback_state_probe")),
|
||||
};
|
||||
return match row.try_get::<usize, bool>(0) {
|
||||
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||
std::result::Result::Err(_) => std::result::Result::Err(LiveFailure::new("conflict_rollback_state_decode")),
|
||||
};
|
||||
}
|
||||
|
||||
async fn variant_projection_is_exact(
|
||||
client: &tokio_postgres::Client,
|
||||
reference: &ksp_store_api::RawTransactionReference,
|
||||
|
||||
Reference in New Issue
Block a user