Skip to main content

sol_parser_sdk/logs/
utils.rs

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