feat: refactor to multi-protocol event streaming system

Major architectural refactor, upgrading from simple logging system to comprehensive multi-protocol Solana DEX event streaming system:

 New Features:
- Support for 5 DEX protocols: PumpFun, PumpSwap, Bonk, Raydium CPMM, Raydium CLMM
- Implement unified event interface (UnifiedEvent trait) and event factory pattern
- Add dual streaming support: Yellowstone gRPC and ShredStream
- Add Chinese documentation (README_CN.md)

🏗️ Architectural Improvements:
- Refactor event parsing system with modular design
- Implement protocol-specific parsers and event types
- Optimize dependency management, update Cargo.toml
- Remove legacy logging modules, clean up redundant code

📊 Statistics:
- Added 46 files, 4511 lines of code
- Removed 1381 lines of legacy code
- Net addition of 3130 lines of code

Tech Stack:
- Rust async/await for asynchronous processing
- Protocol Buffers support
- Multi-protocol event parsing
- High-performance event stream subscription
This commit is contained in:
ysq
2025-07-19 23:46:42 +08:00
parent a7c9721877
commit 9e34a01874
46 changed files with 4511 additions and 1381 deletions
+54
View File
@@ -0,0 +1,54 @@
pub mod types;
pub mod utils;
/// 自动生成UnifiedEvent trait实现的宏
#[macro_export]
macro_rules! impl_unified_event {
// 带有自定义ID表达式的版本
($struct_name:ident, $($field:ident),*) => {
impl $crate::streaming::event_parser::core::traits::UnifiedEvent for $struct_name {
fn id(&self) -> &str {
&self.metadata.id
}
fn event_type(&self) -> $crate::streaming::event_parser::common::types::EventType {
self.metadata.event_type.clone()
}
fn signature(&self) -> &str {
&self.metadata.signature
}
fn slot(&self) -> u64 {
self.metadata.slot
}
fn program_received_time_ms(&self) -> i64 {
self.metadata.program_received_time_ms
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn clone_boxed(&self) -> Box<dyn $crate::streaming::event_parser::core::traits::UnifiedEvent> {
Box::new(self.clone())
}
fn merge(&mut self, other: Box<dyn $crate::streaming::event_parser::core::traits::UnifiedEvent>) {
if let Some(e) = other.as_any().downcast_ref::<$struct_name>() {
$(
self.$field = e.$field.clone();
)*
}
}
}
};
}
pub use types::*;
pub use utils::*;
+173
View File
@@ -0,0 +1,173 @@
use borsh::{BorshDeserialize, BorshSerialize};
use serde::{Deserialize, Serialize};
use solana_sdk::pubkey::Pubkey;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
#[derive(
Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize,
)]
pub enum ProtocolType {
#[default]
PumpSwap,
PumpFun,
Bonk,
RaydiumCpmm,
RaydiumClmm,
}
/// 事件类型枚举
#[derive(
Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize,
)]
pub enum EventType {
// PumpSwap 事件
#[default]
PumpSwapBuy,
PumpSwapSell,
PumpSwapCreatePool,
PumpSwapDeposit,
PumpSwapWithdraw,
// PumpFun 事件
PumpFunCreateToken,
PumpFunBuy,
PumpFunSell,
// Bonk 事件
BonkBuyExactIn,
BonkBuyExactOut,
BonkSellExactIn,
BonkSellExactOut,
BonkInitialize,
// Raydium CPMM 事件
RaydiumCpmmSwapBaseInput,
RaydiumCpmmSwapBaseOutput,
// Raydium CLMM 事件
RaydiumClmmSwap,
RaydiumClmmSwapV2,
// 通用事件
Unknown,
}
impl EventType {
pub fn to_string(&self) -> String {
match self {
EventType::PumpSwapBuy => "PumpSwapBuy".to_string(),
EventType::PumpSwapSell => "PumpSwapSell".to_string(),
EventType::PumpSwapCreatePool => "PumpSwapCreatePool".to_string(),
EventType::PumpSwapDeposit => "PumpSwapDeposit".to_string(),
EventType::PumpSwapWithdraw => "PumpSwapWithdraw".to_string(),
EventType::PumpFunCreateToken => "PumpFunCreateToken".to_string(),
EventType::PumpFunBuy => "PumpFunBuy".to_string(),
EventType::PumpFunSell => "PumpFunSell".to_string(),
EventType::BonkBuyExactIn => "BonkBuyExactIn".to_string(),
EventType::BonkBuyExactOut => "BonkBuyExactOut".to_string(),
EventType::BonkSellExactIn => "BonkSellExactIn".to_string(),
EventType::BonkSellExactOut => "BonkSellExactOut".to_string(),
EventType::BonkInitialize => "BonkInitialize".to_string(),
EventType::RaydiumCpmmSwapBaseInput => "RaydiumCpmmSwapBaseInput".to_string(),
EventType::RaydiumCpmmSwapBaseOutput => "RaydiumCpmmSwapBaseOutput".to_string(),
EventType::RaydiumClmmSwap => "RaydiumClmmSwap".to_string(),
EventType::RaydiumClmmSwapV2 => "RaydiumClmmSwapV2".to_string(),
EventType::Unknown => "Unknown".to_string(),
}
}
}
/// 解析结果
#[derive(Debug, Clone)]
pub struct ParseResult<T> {
pub success: bool,
pub data: Option<T>,
pub error: Option<String>,
}
impl<T> ParseResult<T> {
pub fn success(data: T) -> Self {
Self {
success: true,
data: Some(data),
error: None,
}
}
pub fn failure(error: String) -> Self {
Self {
success: false,
data: None,
error: Some(error),
}
}
pub fn is_success(&self) -> bool {
self.success
}
pub fn is_failure(&self) -> bool {
!self.success
}
}
/// 协议信息
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProtocolInfo {
pub name: String,
pub program_ids: Vec<Pubkey>,
}
impl ProtocolInfo {
pub fn new(name: String, program_ids: Vec<Pubkey>) -> Self {
Self { name, program_ids }
}
pub fn supports_program(&self, program_id: &Pubkey) -> bool {
self.program_ids.contains(program_id)
}
}
/// 事件元数据
#[derive(
Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize,
)]
pub struct EventMetadata {
pub id: String,
pub signature: String,
pub slot: u64,
pub program_received_time_ms: i64,
pub protocol: ProtocolType,
pub event_type: EventType,
pub program_id: Pubkey,
}
impl EventMetadata {
pub fn new(
id: String,
signature: String,
slot: u64,
protocol: ProtocolType,
event_type: EventType,
program_id: Pubkey,
) -> Self {
Self {
id,
signature,
slot,
program_received_time_ms: chrono::Utc::now().timestamp_millis(),
protocol,
event_type,
program_id,
}
}
pub fn set_id(&mut self, id: String) {
let _id = format!("{}-{}-{}", self.signature, self.event_type.to_string(), id);
// 对传入的 id 进行哈希处理
let mut hasher = DefaultHasher::new();
_id.hash(&mut hasher);
let hash_value = hasher.finish();
self.id = format!("{:x}", hash_value);
}
}
+111
View File
@@ -0,0 +1,111 @@
use base64::engine::general_purpose;
use base64::Engine;
use std::time::{SystemTime, UNIX_EPOCH};
/// 获取当前时间戳
pub fn current_timestamp() -> i64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_secs() as i64
}
/// 从base64字符串解码数据
pub fn decode_base64(data: &str) -> Result<Vec<u8>, base64::DecodeError> {
general_purpose::STANDARD.decode(data)
}
/// 将数据编码为base64字符串
pub fn encode_base64(data: &[u8]) -> String {
general_purpose::STANDARD.encode(data)
}
/// 从字节数组中提取鉴别器和剩余数据
pub fn extract_discriminator(length: usize, data: &[u8]) -> Option<(&[u8], &[u8])> {
if data.len() < length {
return None;
}
Some((&data[..length], &data[length..]))
}
/// 检查鉴别器是否匹配
pub fn discriminator_matches(data: &str, expected: &str) -> bool {
if data.len() < expected.len() {
return false;
}
&data[..expected.len()] == expected
}
/// 从日志中提取程序数据
pub fn extract_program_data(log: &str) -> Option<&str> {
const PROGRAM_DATA_PREFIX: &str = "Program data: ";
log.strip_prefix(PROGRAM_DATA_PREFIX)
}
/// 从日志中提取程序日志
pub fn extract_program_log<'a>(log: &'a str, prefix: &str) -> Option<&'a str> {
log.strip_prefix(prefix)
}
/// 安全地从字节数组中读取u64
pub fn read_u64_le(data: &[u8], offset: usize) -> Option<u64> {
if data.len() < offset + 8 {
return None;
}
let bytes: [u8; 8] = data[offset..offset + 8].try_into().ok()?;
Some(u64::from_le_bytes(bytes))
}
pub fn read_u128_le(data: &[u8], offset: usize) -> Option<u128> {
if data.len() < offset + 16 {
return None;
}
let bytes: [u8; 16] = data[offset..offset + 16].try_into().ok()?;
Some(u128::from_le_bytes(bytes))
}
pub fn read_u8_le(data: &[u8], offset: usize) -> Option<u8> {
if data.len() < offset + 1 {
return None;
}
let bytes: [u8; 1] = data[offset..offset + 1].try_into().ok()?;
Some(u8::from_le_bytes(bytes))
}
/// 安全地从字节数组中读取u32
pub fn read_u32_le(data: &[u8], offset: usize) -> Option<u32> {
if data.len() < offset + 4 {
return None;
}
let bytes: [u8; 4] = data[offset..offset + 4].try_into().ok()?;
Some(u32::from_le_bytes(bytes))
}
/// 安全地从字节数组中读取u16
pub fn read_u16_le(data: &[u8], offset: usize) -> Option<u16> {
if data.len() < offset + 2 {
return None;
}
let bytes: [u8; 2] = data[offset..offset + 2].try_into().ok()?;
Some(u16::from_le_bytes(bytes))
}
/// 安全地从字节数组中读取u8
pub fn read_u8(data: &[u8], offset: usize) -> Option<u8> {
data.get(offset).copied()
}
/// 验证账户索引的有效性
pub fn validate_account_indices(indices: &[u8], account_count: usize) -> bool {
indices.iter().all(|&idx| (idx as usize) < account_count)
}
/// 格式化公钥为短字符串
pub fn format_pubkey_short(pubkey: &solana_sdk::pubkey::Pubkey) -> String {
let s = pubkey.to_string();
if s.len() <= 8 {
s
} else {
format!("{}...{}", &s[..4], &s[s.len() - 4..])
}
}