This commit is contained in:
2026-05-11 11:02:47 +02:00
parent d66afede28
commit 7f130dba6b
49 changed files with 10301 additions and 8481 deletions

View File

@@ -0,0 +1,337 @@
// file: kb_lib/src/db/queries/protocol_candidate.rs
//! Queries for `k_sol_protocol_candidates`.
/// Inserts one protocol candidate row.
pub async fn query_protocol_candidates_insert(
database: &crate::Database,
dto: &crate::ProtocolCandidateDto,
) -> Result<i64, crate::Error> {
let slot_i64 = match dto.slot {
Some(slot) => {
let slot_result = i64::try_from(slot);
match slot_result {
Ok(slot) => Some(slot),
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot convert protocol candidate slot '{}' to i64: {}",
slot, error
)));
},
}
},
None => None,
};
match database.connection() {
crate::DatabaseConnection::Sqlite(pool) => {
let query_result = sqlx::query(
r#"
INSERT INTO k_sol_protocol_candidates (
transaction_id,
instruction_id,
signature,
slot,
program_id,
program_name_hint,
candidate_protocol,
candidate_surface,
reason,
evidence_json,
created_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
"#,
)
.bind(dto.transaction_id)
.bind(dto.instruction_id)
.bind(dto.signature.clone())
.bind(slot_i64)
.bind(dto.program_id.clone())
.bind(dto.program_name_hint.clone())
.bind(dto.candidate_protocol.clone())
.bind(dto.candidate_surface.clone())
.bind(dto.reason.clone())
.bind(dto.evidence_json.clone())
.bind(dto.created_at.to_rfc3339())
.execute(pool)
.await;
match query_result {
Ok(query_result) => return Ok(query_result.last_insert_rowid()),
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot insert k_sol_protocol_candidates on sqlite: {}",
error
)));
},
}
},
}
}
/// Deletes protocol candidates for one transaction.
///
/// This is useful before recomputing candidates for a replayed transaction.
pub async fn query_protocol_candidates_delete_by_transaction_id(
database: &crate::Database,
transaction_id: i64,
) -> Result<u64, crate::Error> {
match database.connection() {
crate::DatabaseConnection::Sqlite(pool) => {
let query_result = sqlx::query(
r#"
DELETE FROM k_sol_protocol_candidates
WHERE transaction_id = ?
"#,
)
.bind(transaction_id)
.execute(pool)
.await;
match query_result {
Ok(query_result) => return Ok(query_result.rows_affected()),
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot delete k_sol_protocol_candidates for transaction_id '{}' on sqlite: {}",
transaction_id, error
)));
},
}
},
}
}
/// Lists protocol candidates for one transaction.
pub async fn query_protocol_candidates_list_by_transaction_id(
database: &crate::Database,
transaction_id: i64,
) -> Result<std::vec::Vec<crate::ProtocolCandidateDto>, crate::Error> {
match database.connection() {
crate::DatabaseConnection::Sqlite(pool) => {
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::ProtocolCandidateEntity>(
r#"
SELECT
id,
transaction_id,
instruction_id,
signature,
slot,
program_id,
program_name_hint,
candidate_protocol,
candidate_surface,
reason,
evidence_json,
created_at
FROM k_sol_protocol_candidates
WHERE transaction_id = ?
ORDER BY id ASC
"#,
)
.bind(transaction_id)
.fetch_all(pool)
.await;
let entities = match query_result {
Ok(entities) => entities,
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot list k_sol_protocol_candidates for transaction_id '{}' on sqlite: {}",
transaction_id, error
)));
},
};
return protocol_candidate_entities_to_dtos(entities);
},
}
}
/// Lists protocol candidates for one program id.
pub async fn query_protocol_candidates_list_by_program_id(
database: &crate::Database,
program_id: &str,
limit: u32,
) -> Result<std::vec::Vec<crate::ProtocolCandidateDto>, crate::Error> {
if limit == 0 {
return Ok(std::vec::Vec::new());
}
match database.connection() {
crate::DatabaseConnection::Sqlite(pool) => {
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::ProtocolCandidateEntity>(
r#"
SELECT
id,
transaction_id,
instruction_id,
signature,
slot,
program_id,
program_name_hint,
candidate_protocol,
candidate_surface,
reason,
evidence_json,
created_at
FROM k_sol_protocol_candidates
WHERE program_id = ?
ORDER BY id DESC
LIMIT ?
"#,
)
.bind(program_id.to_string())
.bind(i64::from(limit))
.fetch_all(pool)
.await;
let entities = match query_result {
Ok(entities) => entities,
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot list k_sol_protocol_candidates for program_id '{}' on sqlite: {}",
program_id, error
)));
},
};
return protocol_candidate_entities_to_dtos(entities);
},
}
}
/// Lists recent protocol candidates ordered from newest to oldest.
pub async fn query_protocol_candidates_list_recent(
database: &crate::Database,
limit: u32,
) -> Result<std::vec::Vec<crate::ProtocolCandidateDto>, crate::Error> {
if limit == 0 {
return Ok(std::vec::Vec::new());
}
match database.connection() {
crate::DatabaseConnection::Sqlite(pool) => {
let query_result = sqlx::query_as::<sqlx::Sqlite, crate::ProtocolCandidateEntity>(
r#"
SELECT
id,
transaction_id,
instruction_id,
signature,
slot,
program_id,
program_name_hint,
candidate_protocol,
candidate_surface,
reason,
evidence_json,
created_at
FROM k_sol_protocol_candidates
ORDER BY id DESC
LIMIT ?
"#,
)
.bind(i64::from(limit))
.fetch_all(pool)
.await;
let entities = match query_result {
Ok(entities) => entities,
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot list recent k_sol_protocol_candidates on sqlite: {}",
error
)));
},
};
return protocol_candidate_entities_to_dtos(entities);
},
}
}
fn protocol_candidate_entities_to_dtos(
entities: std::vec::Vec<crate::ProtocolCandidateEntity>,
) -> Result<std::vec::Vec<crate::ProtocolCandidateDto>, crate::Error> {
let mut dtos = std::vec::Vec::new();
for entity in entities {
let dto_result = crate::ProtocolCandidateDto::try_from(entity);
let dto = match dto_result {
Ok(dto) => dto,
Err(error) => return Err(error),
};
dtos.push(dto);
}
return Ok(dtos);
}
/// Lists protocol candidate summaries ordered by investigation priority.
pub async fn query_protocol_candidate_summaries_list_by_priority(
database: &crate::Database,
limit: u32,
) -> Result<std::vec::Vec<crate::ProtocolCandidateSummaryDto>, crate::Error> {
if limit == 0 {
return Ok(std::vec::Vec::new());
}
match database.connection() {
crate::DatabaseConnection::Sqlite(pool) => {
let query_result =
sqlx::query_as::<sqlx::Sqlite, crate::ProtocolCandidateSummaryEntity>(
r#"
WITH grouped AS (
SELECT
program_id,
program_name_hint,
candidate_protocol,
candidate_surface,
reason,
COUNT(*) AS occurrence_count,
COUNT(DISTINCT signature) AS transaction_count,
MAX(slot) AS last_slot,
MAX(id) AS latest_candidate_id
FROM k_sol_protocol_candidates
GROUP BY
program_id,
program_name_hint,
candidate_protocol,
candidate_surface,
reason
)
SELECT
grouped.program_id,
grouped.program_name_hint,
grouped.candidate_protocol,
grouped.candidate_surface,
grouped.reason,
grouped.occurrence_count,
grouped.transaction_count,
grouped.last_slot,
grouped.latest_candidate_id,
latest.signature AS latest_signature,
latest.created_at AS latest_created_at
FROM grouped
JOIN k_sol_protocol_candidates latest
ON latest.id = grouped.latest_candidate_id
ORDER BY
grouped.transaction_count DESC,
grouped.occurrence_count DESC,
grouped.last_slot DESC,
grouped.latest_candidate_id DESC
LIMIT ?
"#,
)
.bind(i64::from(limit))
.fetch_all(pool)
.await;
let entities = match query_result {
Ok(entities) => entities,
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot list k_sol_protocol_candidates summaries on sqlite: {}",
error
)));
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
let dto_result = crate::ProtocolCandidateSummaryDto::try_from(entity);
let dto = match dto_result {
Ok(dto) => dto,
Err(error) => return Err(error),
};
dtos.push(dto);
}
return Ok(dtos);
},
}
}

View File

@@ -0,0 +1,263 @@
// file: kb_lib/src/db/queries/transaction_classification.rs
//! Queries for `k_sol_transaction_classifications`.
/// Inserts or updates one transaction classification row.
pub async fn query_transaction_classifications_upsert(
database: &crate::Database,
dto: &crate::TransactionClassificationDto,
) -> Result<i64, crate::Error> {
let slot_i64 = match dto.slot {
Some(slot) => {
let slot_result = i64::try_from(slot);
match slot_result {
Ok(slot) => Some(slot),
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot convert transaction classification slot '{}' to i64: {}",
slot, error
)));
},
}
},
None => None,
};
match database.connection() {
crate::DatabaseConnection::Sqlite(pool) => {
let query_result = sqlx::query(
r#"
INSERT INTO k_sol_transaction_classifications (
transaction_id,
signature,
slot,
classification_kind,
primary_protocol,
primary_program_id,
confidence_level,
reason,
evidence_json,
created_at,
updated_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(transaction_id) DO UPDATE SET
signature = excluded.signature,
slot = excluded.slot,
classification_kind = excluded.classification_kind,
primary_protocol = excluded.primary_protocol,
primary_program_id = excluded.primary_program_id,
confidence_level = excluded.confidence_level,
reason = excluded.reason,
evidence_json = excluded.evidence_json,
updated_at = excluded.updated_at
"#,
)
.bind(dto.transaction_id)
.bind(dto.signature.clone())
.bind(slot_i64)
.bind(dto.classification_kind.clone())
.bind(dto.primary_protocol.clone())
.bind(dto.primary_program_id.clone())
.bind(i64::from(dto.confidence_level))
.bind(dto.reason.clone())
.bind(dto.evidence_json.clone())
.bind(dto.created_at.to_rfc3339())
.bind(dto.updated_at.to_rfc3339())
.execute(pool)
.await;
if let Err(error) = query_result {
return Err(crate::Error::Db(format!(
"cannot upsert k_sol_transaction_classifications on sqlite: {}",
error
)));
}
let id_result = sqlx::query_scalar::<sqlx::Sqlite, i64>(
r#"
SELECT id
FROM k_sol_transaction_classifications
WHERE transaction_id = ?
LIMIT 1
"#,
)
.bind(dto.transaction_id)
.fetch_one(pool)
.await;
match id_result {
Ok(id) => return Ok(id),
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot fetch k_sol_transaction_classifications id for transaction_id '{}' on sqlite: {}",
dto.transaction_id, error
)));
},
}
},
}
}
/// Reads one transaction classification by transaction id.
pub async fn query_transaction_classifications_get_by_transaction_id(
database: &crate::Database,
transaction_id: i64,
) -> Result<std::option::Option<crate::TransactionClassificationDto>, crate::Error> {
match database.connection() {
crate::DatabaseConnection::Sqlite(pool) => {
let query_result =
sqlx::query_as::<sqlx::Sqlite, crate::TransactionClassificationEntity>(
r#"
SELECT
id,
transaction_id,
signature,
slot,
classification_kind,
primary_protocol,
primary_program_id,
confidence_level,
reason,
evidence_json,
created_at,
updated_at
FROM k_sol_transaction_classifications
WHERE transaction_id = ?
LIMIT 1
"#,
)
.bind(transaction_id)
.fetch_optional(pool)
.await;
let entity_option = match query_result {
Ok(entity_option) => entity_option,
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot fetch k_sol_transaction_classifications for transaction_id '{}' on sqlite: {}",
transaction_id, error
)));
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::TransactionClassificationDto::try_from(entity);
match dto_result {
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
},
None => return Ok(None),
}
},
}
}
/// Reads one transaction classification by signature.
pub async fn query_transaction_classifications_get_by_signature(
database: &crate::Database,
signature: &str,
) -> Result<std::option::Option<crate::TransactionClassificationDto>, crate::Error> {
match database.connection() {
crate::DatabaseConnection::Sqlite(pool) => {
let query_result =
sqlx::query_as::<sqlx::Sqlite, crate::TransactionClassificationEntity>(
r#"
SELECT
id,
transaction_id,
signature,
slot,
classification_kind,
primary_protocol,
primary_program_id,
confidence_level,
reason,
evidence_json,
created_at,
updated_at
FROM k_sol_transaction_classifications
WHERE signature = ?
LIMIT 1
"#,
)
.bind(signature.to_string())
.fetch_optional(pool)
.await;
let entity_option = match query_result {
Ok(entity_option) => entity_option,
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot fetch k_sol_transaction_classifications for signature '{}' on sqlite: {}",
signature, error
)));
},
};
match entity_option {
Some(entity) => {
let dto_result = crate::TransactionClassificationDto::try_from(entity);
match dto_result {
Ok(dto) => return Ok(Some(dto)),
Err(error) => return Err(error),
}
},
None => return Ok(None),
}
},
}
}
/// Lists recent transaction classifications ordered from newest to oldest.
pub async fn query_transaction_classifications_list_recent(
database: &crate::Database,
limit: u32,
) -> Result<std::vec::Vec<crate::TransactionClassificationDto>, crate::Error> {
if limit == 0 {
return Ok(std::vec::Vec::new());
}
match database.connection() {
crate::DatabaseConnection::Sqlite(pool) => {
let query_result =
sqlx::query_as::<sqlx::Sqlite, crate::TransactionClassificationEntity>(
r#"
SELECT
id,
transaction_id,
signature,
slot,
classification_kind,
primary_protocol,
primary_program_id,
confidence_level,
reason,
evidence_json,
created_at,
updated_at
FROM k_sol_transaction_classifications
ORDER BY id DESC
LIMIT ?
"#,
)
.bind(i64::from(limit))
.fetch_all(pool)
.await;
let entities = match query_result {
Ok(entities) => entities,
Err(error) => {
return Err(crate::Error::Db(format!(
"cannot list k_sol_transaction_classifications on sqlite: {}",
error
)));
},
};
let mut dtos = std::vec::Vec::new();
for entity in entities {
let dto_result = crate::TransactionClassificationDto::try_from(entity);
let dto = match dto_result {
Ok(dto) => dto,
Err(error) => return Err(error),
};
dtos.push(dto);
}
return Ok(dtos);
},
}
}