Skip to main content

sol_parser_sdk/logs/
utils.rs

1//! 日志解析通用工具函数
2//!
3//! 提供字节数据解析的基础工具,不使用 BorshDeserialize
4
5use crate::core::clock::now_us;
6use crate::core::events::EventMetadata;
7#[cfg(target_os = "windows")]
8use crate::core::now_micros;
9use base64::{engine::general_purpose, Engine as _};
10use solana_sdk::{pubkey::Pubkey, signature::Signature};
11
12/// 从日志中提取程序数据(使用 SIMD 优化查找)
13#[inline]
14pub fn extract_program_data(log: &str) -> Option<Vec<u8>> {
15    use memchr::memmem;
16
17    let log_bytes = log.as_bytes();
18    let pos = memmem::find(log_bytes, b"Program data: ")?;
19
20    let data_part = &log[pos + 14..];
21    general_purpose::STANDARD.decode(data_part.trim()).ok()
22}
23
24/// 快速提取 discriminator(只解码前16字节,避免完整解码)
25#[inline]
26pub fn extract_discriminator_fast(log: &str) -> Option<[u8; 8]> {
27    use memchr::memmem;
28
29    let log_bytes = log.as_bytes();
30    let pos = memmem::find(log_bytes, b"Program data: ")?;
31
32    let data_part = log[pos + 14..].trim();
33
34    // Base64 编码:每4个字符解码为3个字节
35    // 要获取8字节,需要至少 ceil(8/3)*4 = 12 个 base64 字符
36    if data_part.len() < 12 {
37        return None;
38    }
39
40    // 取前16个字符(解码为12字节,包含8字节 discriminator)
41    let prefix = &data_part[..16];
42
43    let mut buf = [0u8; 12];
44    let decoded_len = general_purpose::STANDARD.decode_slice(prefix.as_bytes(), &mut buf).ok()?;
45
46    if decoded_len >= 8 {
47        Some(buf[0..8].try_into().unwrap())
48    } else {
49        None
50    }
51}
52
53/// 从字节数组中读取 u64(小端序)- SIMD 优化
54#[inline]
55pub fn read_u64_le(data: &[u8], offset: usize) -> Option<u64> {
56    data.get(offset..offset + 8).map(|slice| u64::from_le_bytes(slice.try_into().unwrap()))
57}
58
59/// 从字节数组中读取 u32(小端序)- SIMD 优化
60#[inline]
61pub fn read_u32_le(data: &[u8], offset: usize) -> Option<u32> {
62    data.get(offset..offset + 4).map(|slice| u32::from_le_bytes(slice.try_into().unwrap()))
63}
64
65/// 从字节数组中读取 i64(小端序)- SIMD 优化
66pub fn read_i64_le(data: &[u8], offset: usize) -> Option<i64> {
67    data.get(offset..offset + 8).map(|slice| i64::from_le_bytes(slice.try_into().unwrap()))
68}
69
70/// 从字节数组中读取 i32(小端序)- SIMD 优化
71pub fn read_i32_le(data: &[u8], offset: usize) -> Option<i32> {
72    data.get(offset..offset + 4).map(|slice| i32::from_le_bytes(slice.try_into().unwrap()))
73}
74
75/// 从字节数组中读取 u128(小端序)- SIMD 优化
76pub fn read_u128_le(data: &[u8], offset: usize) -> Option<u128> {
77    data.get(offset..offset + 16).map(|slice| u128::from_le_bytes(slice.try_into().unwrap()))
78}
79
80/// 从字节数组中读取 u16(小端序)- SIMD 优化
81pub fn read_u16_le(data: &[u8], offset: usize) -> Option<u16> {
82    data.get(offset..offset + 2).map(|slice| u16::from_le_bytes(slice.try_into().unwrap()))
83}
84
85/// 从字节数组中读取 u8
86pub fn read_u8(data: &[u8], offset: usize) -> Option<u8> {
87    data.get(offset).copied()
88}
89
90/// 从字节数组中读取 Pubkey(32字节)- SIMD 优化
91#[inline]
92pub fn read_pubkey(data: &[u8], offset: usize) -> Option<Pubkey> {
93    data.get(offset..offset + 32).and_then(|slice| {
94        let key_bytes: [u8; 32] = slice.try_into().ok()?;
95        Some(Pubkey::new_from_array(key_bytes))
96    })
97}
98
99/// 从字节数组中读取字符串(分配版本,向后兼容)
100pub fn read_string(data: &[u8], offset: usize) -> Option<(String, usize)> {
101    let (string_ref, consumed) = read_string_ref(data, offset)?;
102    Some((string_ref.to_string(), consumed))
103}
104
105/// 从字节数组中读取字符串引用(零拷贝版本)
106///
107/// ## 零延迟优化
108/// 返回 &str 引用而不是 String,避免 50-100ns 的堆分配开销
109///
110/// ## 用法
111/// ```ignore
112/// let (name_ref, consumed) = read_string_ref(data, offset)?;
113/// // 直接使用引用,无需分配
114/// println!("Name: {}", name_ref);
115/// ```
116#[inline(always)] // 零延迟优化:内联热路径
117pub fn read_string_ref(data: &[u8], offset: usize) -> Option<(&str, usize)> {
118    if data.len() < offset + 4 {
119        return None;
120    }
121
122    let len = read_u32_le(data, offset)? as usize;
123    if data.len() < offset + 4 + len {
124        return None;
125    }
126
127    let string_bytes = &data[offset + 4..offset + 4 + len];
128    let string_ref = std::str::from_utf8(string_bytes).ok()?; // 零拷贝
129    Some((string_ref, 4 + len))
130}
131
132/// 读取布尔值
133pub fn read_bool(data: &[u8], offset: usize) -> Option<bool> {
134    if data.len() <= offset {
135        return None;
136    }
137    Some(data[offset] == 1)
138}
139
140/// 将 timestamp (seconds, nanos) 转换为微秒表示
141pub fn timestamp_to_microseconds(seconds: i64, nanos: i32) -> i128 {
142    // 用 i128 避免溢出
143    seconds as i128 * 1_000_000 + (nanos as i128 / 1_000)
144}
145
146/// 创建事件元数据的通用函数
147pub fn create_metadata_simple(
148    signature: Signature,
149    slot: u64,
150    tx_index: u64,
151    block_time_us: Option<i64>,
152    program_id: Pubkey,
153    grpc_recv_us: i64,
154) -> EventMetadata {
155    EventMetadata {
156        signature,
157        slot,
158        tx_index,
159        block_time_us: block_time_us.unwrap_or(0),
160        grpc_recv_us,
161        recent_blockhash: None,
162    }
163}
164
165/// 创建默认事件元数据的通用函数(不需要程序ID)
166pub fn create_metadata_default(
167    signature: Signature,
168    slot: u64,
169    tx_index: u64,
170    block_time_us: Option<i64>,
171) -> EventMetadata {
172    let current_time = now_us();
173    EventMetadata {
174        signature,
175        slot,
176        tx_index,
177        block_time_us: block_time_us.unwrap_or(0),
178        grpc_recv_us: current_time,
179        recent_blockhash: None,
180    }
181}
182
183/// 文本回退解析工具
184pub mod text_parser {
185
186    /// 从文本中提取数字
187    pub fn extract_number_from_text(text: &str, field: &str) -> Option<u64> {
188        if let Some(start) = text.find(&format!("{}:", field)) {
189            let after_colon = &text[start + field.len() + 1..];
190            if let Some(end) = after_colon.find(' ').or_else(|| after_colon.find(',')) {
191                after_colon[..end].trim().parse().ok()
192            } else {
193                after_colon.trim().parse().ok()
194            }
195        } else {
196            None
197        }
198    }
199
200    /// 从文本中提取字段值(分配版本,向后兼容)
201    pub fn extract_text_field(text: &str, field: &str) -> Option<String> {
202        extract_text_field_ref(text, field).map(|s| s.to_string())
203    }
204
205    /// 从文本中提取字段值引用(零拷贝版本)
206    ///
207    /// ## 零延迟优化
208    /// 返回 &str 引用而不是 String,避免 50-100ns 的堆分配开销
209    ///
210    /// ## 用法
211    /// ```ignore
212    /// let value_ref = extract_text_field_ref(log, "amount")?;
213    /// let amount: u64 = value_ref.parse().ok()?;
214    /// ```
215    #[inline(always)] // 零延迟优化:内联热路径
216    pub fn extract_text_field_ref<'a>(text: &'a str, field: &str) -> Option<&'a str> {
217        let start = text.find(&format!("{}:", field))?;
218        let after_colon = &text[start + field.len() + 1..];
219        if let Some(end) = after_colon.find(',').or_else(|| after_colon.find(' ')) {
220            Some(after_colon[..end].trim())
221        } else {
222            Some(after_colon.trim())
223        }
224    }
225
226    /// 检测交易类型
227    pub fn detect_trade_type(log: &str) -> Option<bool> {
228        if log.contains("buy") || log.contains("Buy") {
229            Some(true)
230        } else if log.contains("sell") || log.contains("Sell") {
231            Some(false)
232        } else {
233            None
234        }
235    }
236}