// file: kb_lib/src/db/queries/program_instruction_diagnostic.rs //! Queries for program instruction diagnostics. /// Lists diagnostic instruction rows for one program id. pub async fn query_program_instruction_diagnostics_list_by_program_id( database: &crate::Database, program_id: &str, limit: u32, ) -> Result, crate::Error> { if limit == 0 { return Ok(std::vec::Vec::new()); } match database.connection() { crate::DatabaseConnection::Sqlite(pool) => { let query_result = sqlx::query_as::( r#" SELECT tx.id AS transaction_id, tx.signature AS signature, tx.slot AS slot, ins.id AS instruction_id, ins.parent_instruction_id AS parent_instruction_id, ins.instruction_index AS instruction_index, ins.inner_instruction_index AS inner_instruction_index, ins.program_id AS program_id, ins.program_name AS program_name, ins.stack_height AS stack_height, ins.accounts_json AS accounts_json, ins.data_json AS data_json, ins.parsed_type AS parsed_type, ins.parsed_json AS parsed_json, tx.meta_json AS meta_json, tx.transaction_json AS transaction_json FROM k_sol_chain_instructions ins JOIN k_sol_chain_transactions tx ON tx.id = ins.transaction_id WHERE ins.program_id = ? ORDER BY tx.slot DESC, tx.id DESC, ins.instruction_index ASC, ins.inner_instruction_index ASC, ins.id ASC 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 program instruction diagnostics for program_id '{}' on sqlite: {}", program_id, error ))); }, }; let mut dtos = std::vec::Vec::new(); for entity in entities { let dto_result = crate::ProgramInstructionDiagnosticDto::try_from(entity); let dto = match dto_result { Ok(dto) => dto, Err(error) => return Err(error), }; dtos.push(dto); } return Ok(dtos); }, } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] struct ProgramInstructionDiscriminatorSummaryKey { program_id: std::string::String, discriminator_hex: std::option::Option, accounts_count: u64, stack_height: std::option::Option, is_inner_instruction: bool, } #[derive(Debug, Clone)] struct ProgramInstructionDiscriminatorSummaryAccumulator { key: ProgramInstructionDiscriminatorSummaryKey, known_instruction_name: std::option::Option, occurrence_count: u64, decoded_event_count: u64, transaction_signatures: std::collections::BTreeSet, latest_slot: std::option::Option, latest_signature: std::string::String, latest_instruction_id: i64, latest_instruction_index: u32, latest_inner_instruction_index: std::option::Option, latest_parsed_type: std::option::Option, latest_decoded_event_kind: std::option::Option, latest_data_json_preview: std::option::Option, latest_accounts_json_preview: std::option::Option, } fn build_program_instruction_discriminator_summaries( rows: std::vec::Vec, ) -> Result, crate::Error> { let mut grouped = std::collections::BTreeMap::< ProgramInstructionDiscriminatorSummaryKey, ProgramInstructionDiscriminatorSummaryAccumulator, >::new(); for row in rows { let summary_row_result = build_summary_row_from_discriminator_entity(row); let summary_row = match summary_row_result { Ok(summary_row) => summary_row, Err(error) => return Err(error), }; let existing = grouped.get_mut(&summary_row.key); match existing { Some(existing) => { existing.occurrence_count += 1; if summary_row.decoded_event_count > 0 { existing.decoded_event_count += 1; } existing.transaction_signatures.insert(summary_row.latest_signature.clone()); if summary_row.latest_instruction_id > existing.latest_instruction_id { existing.latest_slot = summary_row.latest_slot; existing.latest_signature = summary_row.latest_signature; existing.latest_instruction_id = summary_row.latest_instruction_id; existing.latest_instruction_index = summary_row.latest_instruction_index; existing.latest_inner_instruction_index = summary_row.latest_inner_instruction_index; existing.latest_parsed_type = summary_row.latest_parsed_type; existing.latest_decoded_event_kind = summary_row.latest_decoded_event_kind; existing.latest_data_json_preview = summary_row.latest_data_json_preview; existing.latest_accounts_json_preview = summary_row.latest_accounts_json_preview; } }, None => { grouped.insert(summary_row.key.clone(), summary_row); }, } } let mut summaries = std::vec::Vec::new(); for (_, accumulator) in grouped { let transaction_count_result = u64::try_from(accumulator.transaction_signatures.len()); let transaction_count = match transaction_count_result { Ok(transaction_count) => transaction_count, Err(error) => { return Err(crate::Error::Db(format!( "cannot convert discriminator summary transaction_count to u64: {}", error ))); }, }; let undecoded_occurrence_count = accumulator.occurrence_count.saturating_sub(accumulator.decoded_event_count); summaries.push(crate::ProgramInstructionDiscriminatorSummaryDto { program_id: accumulator.key.program_id, discriminator_hex: accumulator.key.discriminator_hex, known_instruction_name: accumulator.known_instruction_name, accounts_count: accumulator.key.accounts_count, stack_height: accumulator.key.stack_height, is_inner_instruction: accumulator.key.is_inner_instruction, occurrence_count: accumulator.occurrence_count, transaction_count, decoded_event_count: accumulator.decoded_event_count, undecoded_occurrence_count, latest_slot: accumulator.latest_slot, latest_signature: accumulator.latest_signature, latest_instruction_id: accumulator.latest_instruction_id, latest_instruction_index: accumulator.latest_instruction_index, latest_inner_instruction_index: accumulator.latest_inner_instruction_index, latest_parsed_type: accumulator.latest_parsed_type, latest_decoded_event_kind: accumulator.latest_decoded_event_kind, latest_data_json_preview: accumulator.latest_data_json_preview, latest_accounts_json_preview: accumulator.latest_accounts_json_preview, }); } summaries.sort_by(|left, right| { right .undecoded_occurrence_count .cmp(&left.undecoded_occurrence_count) .then(right.transaction_count.cmp(&left.transaction_count)) .then(right.occurrence_count.cmp(&left.occurrence_count)) .then(right.latest_instruction_id.cmp(&left.latest_instruction_id)) }); return Ok(summaries); } fn build_summary_row_from_discriminator_entity( row: crate::ProgramInstructionDiscriminatorRowEntity, ) -> Result { let program_id = match row.program_id.clone() { Some(program_id) => program_id, None => "unknown".to_string(), }; let accounts_count = accounts_count_from_json(row.accounts_json.as_str()); let stack_height = match row.stack_height { Some(stack_height) => match u32::try_from(stack_height) { Ok(stack_height) => Some(stack_height), Err(error) => { return Err(crate::Error::Db(format!( "cannot convert discriminator summary stack_height '{}' to u32: {}", stack_height, error ))); }, }, None => None, }; let instruction_index = match u32::try_from(row.instruction_index) { Ok(instruction_index) => instruction_index, Err(error) => { return Err(crate::Error::Db(format!( "cannot convert discriminator summary instruction_index '{}' to u32: {}", row.instruction_index, error ))); }, }; let inner_instruction_index = match row.inner_instruction_index { Some(inner_instruction_index) => match u32::try_from(inner_instruction_index) { Ok(inner_instruction_index) => Some(inner_instruction_index), Err(error) => { return Err(crate::Error::Db(format!( "cannot convert discriminator summary inner_instruction_index '{}' to u32: {}", inner_instruction_index, error ))); }, }, None => None, }; let latest_slot = match row.slot { Some(slot) => match u64::try_from(slot) { Ok(slot) => Some(slot), Err(error) => { return Err(crate::Error::Db(format!( "cannot convert discriminator summary slot '{}' to u64: {}", slot, error ))); }, }, None => None, }; let decoded_data = decode_instruction_data_json(row.data_json.as_ref()); let discriminator_hex = match decoded_data { Some(decoded_data) => first_8_bytes_hex(decoded_data.as_slice()), None => None, }; let known_instruction_name = known_instruction_name_for_program_discriminator( program_id.as_str(), discriminator_hex.as_deref(), ); let mut transaction_signatures = std::collections::BTreeSet::new(); transaction_signatures.insert(row.signature.clone()); let decoded_event_count = if row.decoded_event_id.is_some() { 1_u64 } else { 0_u64 }; let key = ProgramInstructionDiscriminatorSummaryKey { program_id, discriminator_hex, accounts_count, stack_height, is_inner_instruction: row.parent_instruction_id.is_some(), }; return Ok(ProgramInstructionDiscriminatorSummaryAccumulator { key, known_instruction_name, occurrence_count: 1, decoded_event_count, transaction_signatures, latest_slot, latest_signature: row.signature, latest_instruction_id: row.instruction_id, latest_instruction_index: instruction_index, latest_inner_instruction_index: inner_instruction_index, latest_parsed_type: row.parsed_type, latest_decoded_event_kind: row.decoded_event_kind, latest_data_json_preview: preview_text(row.data_json.as_deref(), 300), latest_accounts_json_preview: preview_text(Some(row.accounts_json.as_str()), 600), }); } fn accounts_count_from_json(accounts_json: &str) -> u64 { let parsed_result = serde_json::from_str::(accounts_json); let parsed = match parsed_result { Ok(parsed) => parsed, Err(_) => return 0, }; let array = match parsed.as_array() { Some(array) => array, None => return 0, }; let count_result = u64::try_from(array.len()); match count_result { Ok(count) => return count, Err(_) => return 0, } } fn decode_instruction_data_json( data_json: std::option::Option<&std::string::String>, ) -> std::option::Option> { let data_json = match data_json { Some(data_json) => data_json, None => return None, }; let parsed_result = serde_json::from_str::(data_json.as_str()); let parsed = match parsed_result { Ok(parsed) => parsed, Err(_) => return None, }; if let serde_json::Value::String(base58_text) = parsed { let decoded_result = decode_base58(base58_text.as_str()); match decoded_result { Ok(decoded) => return Some(decoded), Err(_) => return None, } } return None; } fn decode_base58(input: &str) -> Result, crate::Error> { let decoded_result = bs58::decode(input).into_vec(); match decoded_result { Ok(decoded) => return Ok(decoded), Err(error) => { return Err(crate::Error::Json(format!( "cannot decode instruction data from base58: {}", error ))); }, } } fn first_8_bytes_hex(bytes: &[u8]) -> std::option::Option { if bytes.len() < 8 { return None; } return Some(format!( "{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}", bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], )); } fn known_instruction_name_for_program_discriminator( program_id: &str, discriminator_hex: std::option::Option<&str>, ) -> std::option::Option { if program_id != crate::METEORA_DLMM_PROGRAM_ID { return None; } let discriminator_hex = match discriminator_hex { Some(discriminator_hex) => discriminator_hex, None => return None, }; let name = match discriminator_hex { "2d9aedd2dd0fa65c" => "initialize_lb_pair", "493b2478ed536cc6" => "initialize_lb_pair2", "2e2729876fb7c840" => "initialize_customizable_permissionless_lb_pair", "f349817e3313f16b" => "initialize_customizable_permissionless_lb_pair2", "f8c69e91e17587c8" => "swap", "414b3f4ceb5b5b88" => "swap2", "fa49652126cf4bb8" => "swap_exact_out", "2bd7f784893cf351" => "swap_exact_out2", "38ade6d0ade49ccd" => "swap_with_price_impact", "235613b94ed44bd3" => "initialize_bin_array", "dbc0ea47bebf6650" => "initialize_position", "b59d59438fb63448" => "add_liquidity", "5055d14818ceb16c" => "remove_liquidity", "e445a52e51cb9a1d" => "anchor_self_cpi_log_event", "70bf65ab1c907fbb" => "claim_fee2", _ => return None, }; return Some(name.to_string()); } fn preview_text( text: std::option::Option<&str>, max_len: usize, ) -> std::option::Option { let text = match text { Some(text) => text, None => return None, }; if text.len() <= max_len { return Some(text.to_string()); } let mut preview = text.chars().take(max_len).collect::(); preview.push_str("..."); return Some(preview); } /// Lists instruction discriminator summaries for one program id. pub async fn query_program_instruction_discriminator_summaries_list_by_program_id( database: &crate::Database, program_id: &str, limit: u32, ) -> Result, crate::Error> { if limit == 0 { return Ok(std::vec::Vec::new()); } match database.connection() { crate::DatabaseConnection::Sqlite(pool) => { let query_result = sqlx::query_as::( r#" SELECT tx.id AS transaction_id, tx.signature AS signature, tx.slot AS slot, ins.id AS instruction_id, ins.parent_instruction_id AS parent_instruction_id, ins.instruction_index AS instruction_index, ins.inner_instruction_index AS inner_instruction_index, ins.program_id AS program_id, ins.stack_height AS stack_height, ins.accounts_json AS accounts_json, ins.data_json AS data_json, ins.parsed_type AS parsed_type, de.id AS decoded_event_id, de.event_kind AS decoded_event_kind FROM k_sol_chain_instructions ins JOIN k_sol_chain_transactions tx ON tx.id = ins.transaction_id LEFT JOIN k_sol_dex_decoded_events de ON de.transaction_id = tx.id AND de.instruction_id = ins.id WHERE ins.program_id = ? ORDER BY tx.slot DESC, tx.id DESC, ins.instruction_index ASC, ins.inner_instruction_index ASC, ins.id ASC LIMIT ? "#, ) .bind(program_id.to_string()) .bind(i64::from(limit)) .fetch_all(pool) .await; let rows = match query_result { Ok(rows) => rows, Err(error) => { return Err(crate::Error::Db(format!( "cannot list program instruction discriminator diagnostics for program_id '{}' on sqlite: {}", program_id, error ))); }, }; return build_program_instruction_discriminator_summaries(rows); }, } }