// file: kb-lib/src/model/replay.rs // version: 3 //! Source-neutral instruction replay input shared by decoders and stores. /// Version of the normalized core replay contract. pub const MD_CORE_REPLAY_INPUT_CONTRACT_VERSION: u32 = 2; /// Decoder replay input containing one instruction plus extracted transaction context. #[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] pub struct MdCoreInstructionReplayInput { /// Version of the normalized core replay contract. pub core_contract_version: u32, /// Stable deduplication key for this replay input. pub replay_input_key: std::string::String, /// Transaction signature as non-empty base58 text. pub signature: std::string::String, /// Transaction slot in Solana unsigned representation. pub slot: u64, /// Stable instruction path, for example `0` or `2/1`. pub instruction_path: std::string::String, /// Program id as non-empty base58 text. pub program_id: std::string::String, /// Optional surface hint supplied by an operator or upstream classifier. pub surface_code_hint: std::option::Option, /// Whether the parent transaction failed on-chain. pub transaction_failed: bool, /// Optional transaction error JSON. pub transaction_err_json: std::option::Option, /// Resolved account keys for the whole transaction. pub account_keys_json: serde_json::Value, /// Instruction accounts for the target instruction. pub instruction_accounts_json: serde_json::Value, /// Optional instruction payload retained by the store. pub instruction_payload_json: std::option::Option, /// Optional deterministic hash of the retained instruction payload. pub instruction_payload_hash: std::option::Option, /// Ordered outer instructions for the transaction. pub outer_instructions_json: serde_json::Value, /// Inner instruction tree or subtree relevant to the target instruction. pub inner_instructions_json: serde_json::Value, /// Ordered logs relevant to the transaction or target instruction. pub logs_json: serde_json::Value, /// Balance changes relevant to the transaction or target instruction. pub balance_changes_json: serde_json::Value, } impl MdCoreInstructionReplayInput { /// Builds a decoder replay input after validating its stable identity and array context. #[allow(clippy::too_many_arguments)] pub fn new( replay_input_key: impl std::convert::Into, signature: impl std::convert::Into, slot: u64, instruction_path: impl std::convert::Into, program_id: impl std::convert::Into, transaction_failed: bool, transaction_err_json: std::option::Option, account_keys_json: serde_json::Value, instruction_accounts_json: serde_json::Value, instruction_payload_json: std::option::Option, instruction_payload_hash: std::option::Option, outer_instructions_json: serde_json::Value, inner_instructions_json: serde_json::Value, logs_json: serde_json::Value, balance_changes_json: serde_json::Value, ) -> kb_core::Result { if !outer_instructions_json.is_array() { return std::result::Result::Err(kb_core::Error::invalid_state( "core replay input outer instructions must be a JSON array", )); } let value = Self { core_contract_version: crate::MD_CORE_REPLAY_INPUT_CONTRACT_VERSION, replay_input_key: replay_input_key.into(), signature: signature.into(), slot, instruction_path: instruction_path.into(), program_id: program_id.into(), surface_code_hint: std::option::Option::None, transaction_failed, transaction_err_json, account_keys_json, instruction_accounts_json, instruction_payload_json, instruction_payload_hash, outer_instructions_json, inner_instructions_json, logs_json, balance_changes_json, }; let validation_result = value.validate(); if let std::result::Result::Err(error) = validation_result { return std::result::Result::Err(error); } return std::result::Result::Ok(value); } /// Validates the stable replay identity and contract version. pub fn validate(&self) -> kb_core::Result<()> { if self.core_contract_version != crate::MD_CORE_REPLAY_INPUT_CONTRACT_VERSION { return std::result::Result::Err(kb_core::Error::invalid_state( "unsupported core replay input contract version", )); } if self.replay_input_key.trim().is_empty() || self.signature.trim().is_empty() || self.instruction_path.trim().is_empty() || self.program_id.trim().is_empty() { return std::result::Result::Err(kb_core::Error::invalid_state( "core replay identity fields must not be empty", )); } if self .instruction_payload_hash .as_deref() .is_some_and(|value| return value.trim().is_empty()) { return std::result::Result::Err(kb_core::Error::invalid_state( "core replay payload hash must not be empty when present", )); } return std::result::Result::Ok(()); } /// Adds an optional stable surface hint after validation. pub fn with_surface_code_hint( mut self, surface_code_hint: std::option::Option, ) -> kb_core::Result { if surface_code_hint .as_deref() .is_some_and(|surface| return surface.trim().is_empty()) { return std::result::Result::Err(kb_core::Error::invalid_state( "core replay input surface hint must not be empty when present", )); } self.surface_code_hint = surface_code_hint; return std::result::Result::Ok(self); } }