0.3.15-pre.012-fix.004
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
// file: crates/ksp-store-postgres-lib/src/raw_transaction.rs
|
||||
// version: 7
|
||||
// version: 8
|
||||
|
||||
pub(crate) mod cursor;
|
||||
|
||||
const DELETE_ARCHIVE_PAYLOAD_SQL: &str = "DELETE FROM ksp_raw_transaction_archive_payloads WHERE signature = $1";
|
||||
const GET_ARCHIVE_PAYLOAD_SQL: &str = "SELECT payload FROM ksp_raw_transaction_archive_payloads WHERE signature = $1";
|
||||
const GET_FIRST_TRANSACTION_OBSERVATION_SQL: &str = "SELECT observation_key, transaction_signature, provider, protocol, acquisition_method, origin, received_at_unix_millis, capture_session_id, commitment, endpoint_id, filter_id, observed_at_unix_millis, source_payload_hash, source_payload_size_bytes FROM ksp_raw_transaction_observations WHERE transaction_signature = $1 ORDER BY received_at_unix_millis ASC, observation_key ASC LIMIT 1";
|
||||
const GET_OBSERVATION_SQL: &str = "SELECT observation_key, transaction_signature, provider, protocol, acquisition_method, origin, received_at_unix_millis, capture_session_id, commitment, endpoint_id, filter_id, observed_at_unix_millis, source_payload_hash, source_payload_size_bytes FROM ksp_raw_transaction_observations WHERE observation_key = $1";
|
||||
const GET_RETENTION_SQL: &str = "SELECT retention_state FROM ksp_raw_transactions WHERE signature = $1";
|
||||
const GET_TOMBSTONE_SQL: &str = "SELECT signature, slot::text AS slot_text, block_time_unix_millis, format_id, format_version, content_hash, retention_state FROM ksp_raw_transactions WHERE signature = $1";
|
||||
@@ -23,12 +24,56 @@ const LOCK_RETENTION_TRANSACTION_SQL: &str =
|
||||
"SELECT block_time_unix_millis, payload, retention_state FROM ksp_raw_transactions WHERE signature = $1 FOR UPDATE";
|
||||
const LOCK_TRANSACTION_SQL: &str = "SELECT signature, slot::text AS slot_text, block_time_unix_millis, format_id, format_version, content_hash, payload, retention_state, NULL::BYTEA AS archive_payload FROM ksp_raw_transactions WHERE signature = $1 FOR UPDATE";
|
||||
const LOCK_TRANSACTION_STATE_SQL: &str = "SELECT retention_state FROM ksp_raw_transactions WHERE signature = $1 FOR UPDATE";
|
||||
const RAW_TRANSACTION_CONTENT_CONFLICT_META_FIELDS: [&str; 15] = [
|
||||
"err",
|
||||
"status",
|
||||
"fee",
|
||||
"preBalances",
|
||||
"postBalances",
|
||||
"innerInstructions",
|
||||
"logMessages",
|
||||
"preTokenBalances",
|
||||
"postTokenBalances",
|
||||
"rewards",
|
||||
"loadedAddresses",
|
||||
"returnData",
|
||||
"computeUnitsConsumed",
|
||||
"costUnits",
|
||||
"accounts",
|
||||
];
|
||||
const RAW_TRANSACTION_CONTENT_CONFLICT_PAYLOAD_FIELDS: [&str; 4] = ["transaction", "meta", "version", "transactionIndex"];
|
||||
const REHYDRATE_TRANSACTION_SQL: &str =
|
||||
"UPDATE ksp_raw_transactions SET block_time_unix_millis = $2, payload = $3, retention_state = 'full' WHERE signature = $1";
|
||||
const UPDATE_ARCHIVED_TRANSACTION_SQL: &str =
|
||||
"UPDATE ksp_raw_transactions SET payload = NULL, retention_state = 'archived' WHERE signature = $1 AND retention_state = 'full'";
|
||||
const UPDATE_PURGED_TRANSACTION_SQL: &str = "UPDATE ksp_raw_transactions SET block_time_unix_millis = NULL, payload = NULL, retention_state = 'purged' WHERE signature = $1 AND retention_state = 'archived'";
|
||||
|
||||
struct RawPayloadContentConflictDiagnostic {
|
||||
available: bool,
|
||||
meta_mismatch: bool,
|
||||
meta_mismatch_fields: std::string::String,
|
||||
other_mismatch: bool,
|
||||
transaction_index_mismatch: bool,
|
||||
transaction_mismatch: bool,
|
||||
version_mismatch: bool,
|
||||
}
|
||||
|
||||
struct RawTransactionContentConflictDiagnostic {
|
||||
block_time_mismatch: bool,
|
||||
content_hash_mismatch: bool,
|
||||
format_id_mismatch: bool,
|
||||
format_version_mismatch: bool,
|
||||
meta_mismatch_fields: std::string::String,
|
||||
payload_bytes_mismatch: bool,
|
||||
payload_diagnostic_available: bool,
|
||||
payload_meta_mismatch: bool,
|
||||
payload_other_mismatch: bool,
|
||||
payload_transaction_index_mismatch: bool,
|
||||
payload_transaction_mismatch: bool,
|
||||
payload_version_mismatch: bool,
|
||||
slot_mismatch: bool,
|
||||
}
|
||||
|
||||
struct RawListDbRow {
|
||||
signature: std::vec::Vec<u8>,
|
||||
slot_text: std::string::String,
|
||||
@@ -619,7 +664,12 @@ pub(crate) async fn persist_raw_transaction_acquisition(
|
||||
let comparison_result = compare_existing_transaction(network, locked, &raw_transaction);
|
||||
let comparison = match comparison_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
std::result::Result::Err(error) => {
|
||||
if error.kind() == crate::PostgresBackendErrorKind::Conflict && error.phase() == "raw_acquisition_content_conflict" {
|
||||
log_raw_transaction_content_conflict_provenance(&sql_transaction, network, &observation).await;
|
||||
}
|
||||
return std::result::Result::Err(error);
|
||||
},
|
||||
};
|
||||
match comparison {
|
||||
ExistingTransactionMatch::Active => ksp_store_api::RawEntityWriteOutcome::AlreadyPresent,
|
||||
@@ -1074,9 +1124,178 @@ fn compare_existing_transaction(
|
||||
if raw_transactions_equal(&stored, incoming) {
|
||||
return std::result::Result::Ok(ExistingTransactionMatch::Active);
|
||||
}
|
||||
log_raw_transaction_content_conflict(network, &stored, incoming);
|
||||
return std::result::Result::Err(conflict("raw_acquisition_content_conflict"));
|
||||
}
|
||||
|
||||
fn raw_transaction_content_conflict_diagnostic(
|
||||
stored: &ksp_store_api::RawTransaction,
|
||||
incoming: &ksp_store_api::RawTransaction,
|
||||
) -> RawTransactionContentConflictDiagnostic {
|
||||
let payload = raw_payload_content_conflict_diagnostic(stored.payload().bytes(), incoming.payload().bytes());
|
||||
return RawTransactionContentConflictDiagnostic {
|
||||
block_time_mismatch: stored.block_time() != incoming.block_time(),
|
||||
content_hash_mismatch: stored.payload().content_hash() != incoming.payload().content_hash(),
|
||||
format_id_mismatch: stored.payload().format_id() != incoming.payload().format_id(),
|
||||
format_version_mismatch: stored.payload().format_version() != incoming.payload().format_version(),
|
||||
meta_mismatch_fields: payload.meta_mismatch_fields,
|
||||
payload_bytes_mismatch: stored.payload().bytes() != incoming.payload().bytes(),
|
||||
payload_diagnostic_available: payload.available,
|
||||
payload_meta_mismatch: payload.meta_mismatch,
|
||||
payload_other_mismatch: payload.other_mismatch,
|
||||
payload_transaction_index_mismatch: payload.transaction_index_mismatch,
|
||||
payload_transaction_mismatch: payload.transaction_mismatch,
|
||||
payload_version_mismatch: payload.version_mismatch,
|
||||
slot_mismatch: stored.slot() != incoming.slot(),
|
||||
};
|
||||
}
|
||||
|
||||
fn raw_payload_content_conflict_diagnostic(stored: &[u8], incoming: &[u8]) -> RawPayloadContentConflictDiagnostic {
|
||||
let stored = serde_json::from_slice::<serde_json::Value>(stored);
|
||||
let incoming = serde_json::from_slice::<serde_json::Value>(incoming);
|
||||
let (stored, incoming) = match (stored, incoming) {
|
||||
(std::result::Result::Ok(serde_json::Value::Object(stored)), std::result::Result::Ok(serde_json::Value::Object(incoming))) => (stored, incoming),
|
||||
_ => {
|
||||
return RawPayloadContentConflictDiagnostic {
|
||||
available: false,
|
||||
meta_mismatch: false,
|
||||
meta_mismatch_fields: "unavailable".to_owned(),
|
||||
other_mismatch: false,
|
||||
transaction_index_mismatch: false,
|
||||
transaction_mismatch: false,
|
||||
version_mismatch: false,
|
||||
};
|
||||
},
|
||||
};
|
||||
let transaction_mismatch = stored.get("transaction") != incoming.get("transaction");
|
||||
let meta_mismatch = stored.get("meta") != incoming.get("meta");
|
||||
let version_mismatch = stored.get("version") != incoming.get("version");
|
||||
let transaction_index_mismatch = stored.get("transactionIndex") != incoming.get("transactionIndex");
|
||||
let other_mismatch = json_object_other_fields_mismatch(&stored, &incoming, RAW_TRANSACTION_CONTENT_CONFLICT_PAYLOAD_FIELDS.as_slice());
|
||||
let meta_mismatch_fields = raw_meta_content_conflict_fields(stored.get("meta"), incoming.get("meta"));
|
||||
return RawPayloadContentConflictDiagnostic {
|
||||
available: true,
|
||||
meta_mismatch,
|
||||
meta_mismatch_fields,
|
||||
other_mismatch,
|
||||
transaction_index_mismatch,
|
||||
transaction_mismatch,
|
||||
version_mismatch,
|
||||
};
|
||||
}
|
||||
|
||||
fn raw_meta_content_conflict_fields(stored: std::option::Option<&serde_json::Value>, incoming: std::option::Option<&serde_json::Value>) -> std::string::String {
|
||||
if stored == incoming {
|
||||
return "none".to_owned();
|
||||
}
|
||||
let (stored, incoming) = match (stored, incoming) {
|
||||
(std::option::Option::Some(serde_json::Value::Object(stored)), std::option::Option::Some(serde_json::Value::Object(incoming))) => (stored, incoming),
|
||||
_ => return "shape".to_owned(),
|
||||
};
|
||||
let mut fields = std::vec::Vec::new();
|
||||
for field in RAW_TRANSACTION_CONTENT_CONFLICT_META_FIELDS {
|
||||
if stored.get(field) != incoming.get(field) {
|
||||
fields.push(field);
|
||||
}
|
||||
}
|
||||
if json_object_other_fields_mismatch(stored, incoming, RAW_TRANSACTION_CONTENT_CONFLICT_META_FIELDS.as_slice()) {
|
||||
fields.push("other");
|
||||
}
|
||||
if fields.is_empty() {
|
||||
return "shape".to_owned();
|
||||
}
|
||||
return fields.join(",");
|
||||
}
|
||||
|
||||
fn json_object_other_fields_mismatch(
|
||||
stored: &serde_json::Map<std::string::String, serde_json::Value>,
|
||||
incoming: &serde_json::Map<std::string::String, serde_json::Value>,
|
||||
known_fields: &[&str],
|
||||
) -> bool {
|
||||
let stored_other = stored.iter().filter(|(key, _)| return !known_fields.contains(&key.as_str())).collect::<std::collections::BTreeMap<_, _>>();
|
||||
let incoming_other = incoming.iter().filter(|(key, _)| return !known_fields.contains(&key.as_str())).collect::<std::collections::BTreeMap<_, _>>();
|
||||
return stored_other != incoming_other;
|
||||
}
|
||||
|
||||
fn log_raw_transaction_content_conflict(
|
||||
network: &ksp_store_api::RawNetworkId,
|
||||
stored: &ksp_store_api::RawTransaction,
|
||||
incoming: &ksp_store_api::RawTransaction,
|
||||
) {
|
||||
let diagnostic = raw_transaction_content_conflict_diagnostic(stored, incoming);
|
||||
ksp_logging_lib::warn!(
|
||||
target: crate::TRACING_TARGET,
|
||||
domain = "store.raw_transaction.content_conflict",
|
||||
network = network.as_str(),
|
||||
slot_mismatch = diagnostic.slot_mismatch,
|
||||
block_time_mismatch = diagnostic.block_time_mismatch,
|
||||
format_id_mismatch = diagnostic.format_id_mismatch,
|
||||
format_version_mismatch = diagnostic.format_version_mismatch,
|
||||
content_hash_mismatch = diagnostic.content_hash_mismatch,
|
||||
payload_bytes_mismatch = diagnostic.payload_bytes_mismatch,
|
||||
payload_diagnostic_available = diagnostic.payload_diagnostic_available,
|
||||
transaction_mismatch = diagnostic.payload_transaction_mismatch,
|
||||
meta_mismatch = diagnostic.payload_meta_mismatch,
|
||||
version_mismatch = diagnostic.payload_version_mismatch,
|
||||
transaction_index_mismatch = diagnostic.payload_transaction_index_mismatch,
|
||||
payload_other_mismatch = diagnostic.payload_other_mismatch,
|
||||
meta_mismatch_fields = diagnostic.meta_mismatch_fields.as_str(),
|
||||
"PostgreSQL Store rejected divergent canonical RAW transaction"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
async fn log_raw_transaction_content_conflict_provenance(
|
||||
sql_transaction: &deadpool_postgres::Transaction<'_>,
|
||||
network: &ksp_store_api::RawNetworkId,
|
||||
incoming_observation: &ksp_store_api::RawTransactionObservation,
|
||||
) {
|
||||
let signature = incoming_observation.transaction().signature();
|
||||
let signature_bytes: &[u8] = signature.as_bytes();
|
||||
let stored_observation = match sql_transaction.query_opt(GET_FIRST_TRANSACTION_OBSERVATION_SQL, &[&signature_bytes]).await {
|
||||
std::result::Result::Ok(std::option::Option::Some(row)) => match raw_observation_db_row(&row) {
|
||||
std::result::Result::Ok(row) => decode_raw_observation_row(network, row).ok(),
|
||||
std::result::Result::Err(_) => std::option::Option::None,
|
||||
},
|
||||
std::result::Result::Ok(std::option::Option::None) | std::result::Result::Err(_) => std::option::Option::None,
|
||||
};
|
||||
let incoming = incoming_observation.provenance();
|
||||
let incoming_endpoint = incoming.endpoint_id().map(|value| return value.as_str()).unwrap_or("none");
|
||||
let incoming_commitment = incoming.commitment().map(|value| return value.as_str()).unwrap_or("none");
|
||||
let stored_available = stored_observation.is_some();
|
||||
let (stored_provider, stored_protocol, stored_method, stored_endpoint, stored_commitment) = match stored_observation.as_ref() {
|
||||
std::option::Option::Some(observation) => {
|
||||
let provenance = observation.provenance();
|
||||
(
|
||||
provenance.provider().as_str(),
|
||||
provenance.protocol().as_str(),
|
||||
provenance.acquisition_method().as_str(),
|
||||
provenance.endpoint_id().map(|value| return value.as_str()).unwrap_or("none"),
|
||||
provenance.commitment().map(|value| return value.as_str()).unwrap_or("none"),
|
||||
)
|
||||
},
|
||||
std::option::Option::None => ("unknown", "unknown", "unknown", "unknown", "unknown"),
|
||||
};
|
||||
ksp_logging_lib::warn!(
|
||||
target: crate::TRACING_TARGET,
|
||||
domain = "store.raw_transaction.content_conflict",
|
||||
network = network.as_str(),
|
||||
stored_observation_available = stored_available,
|
||||
stored_provider = stored_provider,
|
||||
stored_protocol = stored_protocol,
|
||||
stored_acquisition_method = stored_method,
|
||||
stored_endpoint_id = stored_endpoint,
|
||||
stored_commitment = stored_commitment,
|
||||
incoming_provider = incoming.provider().as_str(),
|
||||
incoming_protocol = incoming.protocol().as_str(),
|
||||
incoming_acquisition_method = incoming.acquisition_method().as_str(),
|
||||
incoming_endpoint_id = incoming_endpoint,
|
||||
incoming_commitment = incoming_commitment,
|
||||
"PostgreSQL Store classified canonical RAW transaction conflict provenance"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
fn raw_transactions_equal(left: &ksp_store_api::RawTransaction, right: &ksp_store_api::RawTransaction) -> bool {
|
||||
return left.reference() == right.reference()
|
||||
&& left.slot() == right.slot()
|
||||
|
||||
Reference in New Issue
Block a user