events optimization

This commit is contained in:
wood
2025-08-03 05:37:04 +08:00
parent 2cbab93346
commit 4c90931e28
17 changed files with 1311 additions and 212 deletions
+1
View File
@@ -62,3 +62,4 @@ arrayref = "0.3.6"
borsh-derive = "1.5.5" borsh-derive = "1.5.5"
indicatif = "0.17.11" indicatif = "0.17.11"
maplit = "1.0.2" maplit = "1.0.2"
env_logger = "0.11.8"
+137 -7
View File
@@ -17,6 +17,9 @@ A lightweight Rust library for real-time event streaming from Solana DEX trading
5. **Unified Event Interface**: Consistent event handling across all supported protocols 5. **Unified Event Interface**: Consistent event handling across all supported protocols
6. **Event Parsing System**: Automatic parsing and categorization of protocol-specific events 6. **Event Parsing System**: Automatic parsing and categorization of protocol-specific events
7. **High Performance**: Optimized for low-latency event processing 7. **High Performance**: Optimized for low-latency event processing
8. **Batch Processing**: Efficient event batching to improve throughput and reduce overhead
9. **Performance Monitoring**: Built-in performance metrics and monitoring capabilities
10. **Memory Optimization**: Object pooling and caching to reduce memory allocations
## Installation ## Installation
@@ -45,6 +48,8 @@ solana-streamer-sdk = "0.1.8"
## Usage Examples ## Usage Examples
### Basic Usage with Performance Monitoring
```rust ```rust
use solana_streamer_sdk::{ use solana_streamer_sdk::{
match_event, match_event,
@@ -70,22 +75,30 @@ use solana_streamer_sdk::{
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> { async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Starting Solana Streamer...");
// Test Yellowstone gRPC with performance monitoring
test_grpc().await?; test_grpc().await?;
// Test ShredStream with performance monitoring
test_shreds().await?; test_shreds().await?;
Ok(()) Ok(())
} }
async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> { async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
println!("Subscribing to GRPC events..."); println!("Subscribing to Yellowstone gRPC events...");
let grpc = YellowstoneGrpc::new( // Create gRPC client with performance monitoring enabled
let grpc = YellowstoneGrpc::new_with_config(
"https://solana-yellowstone-grpc.publicnode.com:443".to_string(), "https://solana-yellowstone-grpc.publicnode.com:443".to_string(),
None, None,
true, // enable performance monitoring
)?; )?;
let callback = create_event_callback(); let callback = create_event_callback();
// Will try to parse corresponding protocol events from transactions // Configure protocols to monitor
let protocols = vec![ let protocols = vec![
Protocol::PumpFun, Protocol::PumpFun,
Protocol::PumpSwap, Protocol::PumpSwap,
@@ -94,19 +107,23 @@ async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
Protocol::RaydiumClmm, Protocol::RaydiumClmm,
]; ];
// Filter accounts // Configure account filtering
let account_include = vec![ let account_include = vec![
PUMPFUN_PROGRAM_ID.to_string(), // Listen to pumpfun program ID PUMPFUN_PROGRAM_ID.to_string(), // Listen to pumpfun program ID
PUMPSWAP_PROGRAM_ID.to_string(), // Listen to pumpswap program ID PUMPSWAP_PROGRAM_ID.to_string(), // Listen to pumpswap program ID
BONK_PROGRAM_ID.to_string(), // Listen to bonk program ID BONK_PROGRAM_ID.to_string(), // Listen to bonk program ID
RAYDIUM_CPMM_PROGRAM_ID.to_string(), // Listen to raydium_cpmm program ID RAYDIUM_CPMM_PROGRAM_ID.to_string(), // Listen to raydium_cpmm program ID
RAYDIUM_CLMM_PROGRAM_ID.to_string(), // Listen to raydium_clmm program ID RAYDIUM_CLMM_PROGRAM_ID.to_string(), // Listen to raydium_clmm program ID
"xxxxxxxx".to_string(), // Listen to xxxxx account
]; ];
let account_exclude = vec![]; let account_exclude = vec![];
let account_required = vec![]; let account_required = vec![];
println!("Starting to listen for events, press Ctrl+C to stop..."); println!("Starting to listen for events, press Ctrl+C to stop...");
println!("Monitoring programs: {:?}", account_include);
println!("Starting subscription...");
// Subscribe with automatic performance monitoring
grpc.subscribe_events_v2( grpc.subscribe_events_v2(
protocols, protocols,
None, None,
@@ -124,7 +141,11 @@ async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> { async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> {
println!("Subscribing to ShredStream events..."); println!("Subscribing to ShredStream events...");
let shred_stream = ShredStreamGrpc::new("http://127.0.0.1:10800".to_string()).await?; // Create ShredStream client with performance monitoring enabled
let shred_stream = ShredStreamGrpc::new_with_config(
"http://127.0.0.1:10800".to_string(),
true, // enable performance monitoring
).await?;
let callback = create_event_callback(); let callback = create_event_callback();
let protocols = vec![ let protocols = vec![
Protocol::PumpFun, Protocol::PumpFun,
@@ -135,6 +156,8 @@ async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> {
]; ];
println!("Listening for events, press Ctrl+C to stop..."); println!("Listening for events, press Ctrl+C to stop...");
// Subscribe with automatic performance monitoring
shred_stream shred_stream
.shredstream_subscribe(protocols, None, callback) .shredstream_subscribe(protocols, None, callback)
.await?; .await?;
@@ -144,6 +167,7 @@ async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> {
fn create_event_callback() -> impl Fn(Box<dyn UnifiedEvent>) { fn create_event_callback() -> impl Fn(Box<dyn UnifiedEvent>) {
|event: Box<dyn UnifiedEvent>| { |event: Box<dyn UnifiedEvent>| {
println!("🎉 Event received! Type: {:?}, ID: {}", event.event_type(), event.id());
match_event!(event, { match_event!(event, {
BonkPoolCreateEvent => |e: BonkPoolCreateEvent| { BonkPoolCreateEvent => |e: BonkPoolCreateEvent| {
// When using grpc, you can get block_time from each event // When using grpc, you can get block_time from each event
@@ -245,28 +269,134 @@ src/
└── main.rs # Example program └── main.rs # Example program
``` ```
## Performance Optimizations
### Recent Performance Improvements
The latest version includes significant performance optimizations that dramatically improve event processing throughput:
#### 1. **Batch Processing System**
- **Event Batching**: Events are now processed in batches (default: 100 events per batch) instead of individually
- **Reduced Callback Overhead**: Batch processing reduces the number of callback invocations by up to 100x
- **Improved Throughput**: Significantly higher event processing rates with lower CPU usage
- **Configurable Batch Size**: Adjustable batch size to balance latency vs throughput
#### 2. **Memory Optimization**
- **Object Pooling**: `EventMetadataPool` and `TransferDataPool` reduce memory allocations
- **Pre-allocated Vectors**: `Vec::with_capacity()` for collections to avoid dynamic resizing
- **Reduced Cloning**: Minimized unnecessary data cloning operations
- **Memory Usage Monitoring**: Real-time memory usage tracking in performance metrics
#### 3. **Caching System**
- **Event Parse Cache**: `EventParseCache` avoids redundant transaction parsing
- **Cache Hit Rate Monitoring**: Track cache effectiveness in performance metrics
- **Intelligent Cache Management**: Automatic cache size management
#### 4. **Performance Monitoring**
- **Real-time Metrics**: Built-in performance monitoring with automatic display
- **Comprehensive Statistics**: Events/second, processing times, memory usage, cache hit rates
- **Configurable Monitoring**: Enable/disable performance monitoring as needed
- **Zero Overhead**: Monitoring can be completely disabled for maximum performance
#### 5. **Concurrent Processing**
- **Async Event Processing**: Non-blocking event handling with `tokio`
- **Parallel Protocol Parsing**: Multiple protocols parsed concurrently
- **Optimized Channel Sizes**: Increased channel capacity (5000) to handle high event volumes
### Performance Metrics
The built-in performance monitoring provides detailed insights:
- **Events Processed**: Total number of events processed
- **Events/Second**: Real-time processing rate (5-second rolling window)
- **Average Processing Time**: Mean time to process events
- **Min/Max Processing Time**: Fastest and slowest processing times
- **Cache Hit Rate**: Percentage of cache hits for event parsing
- **Memory Usage**: Estimated memory consumption
### Configuration Options
```rust
// Performance monitoring configuration
let grpc = YellowstoneGrpc::new_with_config(
endpoint,
x_token,
true, // enable performance monitoring
)?;
// Batch processing is automatically enabled with optimal settings
// Batch size: 100 events
// Batch timeout: 10ms
// Channel size: 5000
```
## Performance Considerations ## Performance Considerations
1. **Connection Management**: Properly handle connection lifecycle and reconnection 1. **Connection Management**: Properly handle connection lifecycle and reconnection
2. **Event Filtering**: Use protocol filtering to reduce unnecessary event processing 2. **Event Filtering**: Use protocol filtering to reduce unnecessary event processing
3. **Memory Management**: Implement proper cleanup for long-running streams 3. **Memory Management**: Implement proper cleanup for long-running streams
4. **Error Handling**: Robust error handling for network issues and service disruptions 4. **Error Handling**: Robust error handling for network issues and service disruptions
5. **Batch Processing**: Leverage batch processing for high-throughput scenarios
6. **Performance Monitoring**: Use built-in metrics to optimize your application
## Configuration Options ## Configuration Options
### Yellowstone gRPC Configuration ### Yellowstone gRPC Configuration
```rust ```rust
// Recommended: Create gRPC client with performance monitoring enabled
let grpc = YellowstoneGrpc::new_with_config(
"https://solana-yellowstone-grpc.publicnode.com:443".to_string(),
None,
true, // enable performance monitoring
)?;
// Alternative: Basic configuration (performance monitoring enabled by default)
let grpc = YellowstoneGrpc::new( let grpc = YellowstoneGrpc::new(
"https://solana-yellowstone-grpc.publicnode.com:443".to_string(), "https://solana-yellowstone-grpc.publicnode.com:443".to_string(),
None, // Custom configuration options None,
)?;
// Maximum performance: Disable performance monitoring
let grpc = YellowstoneGrpc::new_with_config(
"https://solana-yellowstone-grpc.publicnode.com:443".to_string(),
None,
false, // disable performance monitoring
)?; )?;
``` ```
### ShredStream Configuration ### ShredStream Configuration
```rust ```rust
// Recommended: Create ShredStream client with performance monitoring enabled
let shred_stream = ShredStreamGrpc::new_with_config(
"http://127.0.0.1:10800".to_string(),
true, // enable performance monitoring
).await?;
// Alternative: Basic configuration (performance monitoring enabled by default)
let shred_stream = ShredStreamGrpc::new("http://127.0.0.1:10800".to_string()).await?; let shred_stream = ShredStreamGrpc::new("http://127.0.0.1:10800".to_string()).await?;
// Maximum performance: Disable performance monitoring
let shred_stream = ShredStreamGrpc::new_with_config(
"http://127.0.0.1:10800".to_string(),
false, // disable performance monitoring
).await?;
```
### Performance Tuning
```rust
// Runtime performance monitoring control
grpc.set_enable_metrics(true).await; // Enable monitoring
grpc.set_enable_metrics(false).await; // Disable monitoring
// Get current performance metrics
let metrics = grpc.get_metrics().await;
println!("Current performance: {:?}", metrics);
// Manual performance metrics display
grpc.print_metrics().await;
``` ```
## License ## License
+43 -4
View File
@@ -17,6 +17,9 @@
5. **统一事件接口**: 在所有支持的协议中保持一致的事件处理 5. **统一事件接口**: 在所有支持的协议中保持一致的事件处理
6. **事件解析系统**: 自动解析和分类协议特定事件 6. **事件解析系统**: 自动解析和分类协议特定事件
7. **高性能**: 针对低延迟事件处理进行优化 7. **高性能**: 针对低延迟事件处理进行优化
8. **批处理优化**: 批量处理事件以减少回调开销
9. **性能监控**: 内置性能指标监控,包括事件处理速度、内存使用等
10. **内存优化**: 对象池和缓存机制减少内存分配
## 安装 ## 安装
@@ -76,11 +79,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
} }
async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> { async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
println!("正在订阅 GRPC 事件..."); println!("正在订阅 Yellowstone gRPC 事件...");
let grpc = YellowstoneGrpc::new( // 创建 gRPC 客户端并启用性能监控
let grpc = YellowstoneGrpc::new_with_config(
"https://solana-yellowstone-grpc.publicnode.com:443".to_string(), "https://solana-yellowstone-grpc.publicnode.com:443".to_string(),
None, None,
true, // 启用性能监控
)?; )?;
let callback = create_event_callback(); let callback = create_event_callback();
@@ -124,7 +129,11 @@ async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> { async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> {
println!("正在订阅 ShredStream 事件..."); println!("正在订阅 ShredStream 事件...");
let shred_stream = ShredStreamGrpc::new("http://127.0.0.1:10800".to_string()).await?; // 创建 ShredStream 客户端并启用性能监控
let shred_stream = ShredStreamGrpc::new_with_config(
"http://127.0.0.1:10800".to_string(),
true, // 启用性能监控
).await?;
let callback = create_event_callback(); let callback = create_event_callback();
let protocols = vec![ let protocols = vec![
Protocol::PumpFun, Protocol::PumpFun,
@@ -251,22 +260,52 @@ src/
2. **事件过滤**: 使用协议过滤减少不必要的事件处理 2. **事件过滤**: 使用协议过滤减少不必要的事件处理
3. **内存管理**: 为长时间运行的流实现适当的清理 3. **内存管理**: 为长时间运行的流实现适当的清理
4. **错误处理**: 对网络问题和服务中断进行健壮的错误处理 4. **错误处理**: 对网络问题和服务中断进行健壮的错误处理
5. **批处理优化**: 使用批处理减少回调开销,提高吞吐量
6. **性能监控**: 启用性能监控以识别瓶颈和优化机会
## 配置选项 ## 配置选项
### Yellowstone gRPC 配置 ### Yellowstone gRPC 配置
```rust ```rust
// 推荐:创建 gRPC 客户端并启用性能监控
let grpc = YellowstoneGrpc::new_with_config(
"https://solana-yellowstone-grpc.publicnode.com:443".to_string(),
None,
true, // 启用性能监控
)?;
// 替代:基本配置(性能监控默认启用)
let grpc = YellowstoneGrpc::new( let grpc = YellowstoneGrpc::new(
"https://solana-yellowstone-grpc.publicnode.com:443".to_string(), "https://solana-yellowstone-grpc.publicnode.com:443".to_string(),
None, // 自定义配置选项 None,
)?;
// 最大性能:禁用性能监控
let grpc = YellowstoneGrpc::new_with_config(
"https://solana-yellowstone-grpc.publicnode.com:443".to_string(),
None,
false, // 禁用性能监控
)?; )?;
``` ```
### ShredStream 配置 ### ShredStream 配置
```rust ```rust
// 推荐:创建 ShredStream 客户端并启用性能监控
let shred_stream = ShredStreamGrpc::new_with_config(
"http://127.0.0.1:10800".to_string(),
true, // 启用性能监控
).await?;
// 替代:基本配置(性能监控默认启用)
let shred_stream = ShredStreamGrpc::new("http://127.0.0.1:10800".to_string()).await?; let shred_stream = ShredStreamGrpc::new("http://127.0.0.1:10800".to_string()).await?;
// 最大性能:禁用性能监控
let shred_stream = ShredStreamGrpc::new_with_config(
"http://127.0.0.1:10800".to_string(),
false, // 禁用性能监控
).await?;
``` ```
## 许可证 ## 许可证
+33 -17
View File
@@ -22,19 +22,24 @@ use solana_streamer_sdk::{
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> { async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Starting Solana Streamer...");
test_grpc().await?; test_grpc().await?;
test_shreds().await?; test_shreds().await?;
Ok(()) Ok(())
} }
async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> { async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
println!("Subscribing to GRPC events..."); println!("Subscribing to Yellowstone gRPC events...");
let grpc = YellowstoneGrpc::new( // enable_metrics 为 true 时,会打印性能指标
let grpc = YellowstoneGrpc::new_with_config(
"https://solana-yellowstone-grpc.publicnode.com:443".to_string(), "https://solana-yellowstone-grpc.publicnode.com:443".to_string(),
None, None,
true,
)?; )?;
println!("GRPC client created successfully");
let callback = create_event_callback(); let callback = create_event_callback();
// Will try to parse corresponding protocol events from transactions // Will try to parse corresponding protocol events from transactions
@@ -46,6 +51,8 @@ async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
Protocol::RaydiumClmm, Protocol::RaydiumClmm,
]; ];
println!("Protocols to monitor: {:?}", protocols);
// Filter accounts // Filter accounts
let account_include = vec![ let account_include = vec![
PUMPFUN_PROGRAM_ID.to_string(), // Listen to pumpfun program ID PUMPFUN_PROGRAM_ID.to_string(), // Listen to pumpfun program ID
@@ -53,12 +60,15 @@ async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
BONK_PROGRAM_ID.to_string(), // Listen to bonk program ID BONK_PROGRAM_ID.to_string(), // Listen to bonk program ID
RAYDIUM_CPMM_PROGRAM_ID.to_string(), // Listen to raydium_cpmm program ID RAYDIUM_CPMM_PROGRAM_ID.to_string(), // Listen to raydium_cpmm program ID
RAYDIUM_CLMM_PROGRAM_ID.to_string(), // Listen to raydium_clmm program ID RAYDIUM_CLMM_PROGRAM_ID.to_string(), // Listen to raydium_clmm program ID
"xxxxxxxx".to_string(), // Listen to xxxxx account
]; ];
let account_exclude = vec![]; let account_exclude = vec![];
let account_required = vec![]; let account_required = vec![];
println!("Starting to listen for events, press Ctrl+C to stop..."); println!("Starting to listen for events, press Ctrl+C to stop...");
println!("Monitoring programs: {:?}", account_include);
println!("Starting subscription...");
grpc.subscribe_events_v2( grpc.subscribe_events_v2(
protocols, protocols,
None, None,
@@ -74,9 +84,14 @@ async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
} }
async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> { async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> {
println!("正在订阅 ShredStream 事件..."); println!("Subscribing to ShredStream events...");
// enable_metrics 为 true 时,会打印性能指标
let shred_stream = ShredStreamGrpc::new_with_config(
"http://127.0.0.1:10800".to_string(),
true,
).await?;
let shred_stream = ShredStreamGrpc::new("http://127.0.0.1:10800".to_string()).await?;
let callback = create_event_callback(); let callback = create_event_callback();
let protocols = vec![ let protocols = vec![
Protocol::PumpFun, Protocol::PumpFun,
@@ -86,7 +101,7 @@ async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> {
Protocol::RaydiumClmm, Protocol::RaydiumClmm,
]; ];
println!("开始监听事件,按 Ctrl+C 停止..."); println!("Listening for events, press Ctrl+C to stop...");
shred_stream shred_stream
.shredstream_subscribe(protocols, None, callback) .shredstream_subscribe(protocols, None, callback)
.await?; .await?;
@@ -96,6 +111,7 @@ async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> {
fn create_event_callback() -> impl Fn(Box<dyn UnifiedEvent>) { fn create_event_callback() -> impl Fn(Box<dyn UnifiedEvent>) {
|event: Box<dyn UnifiedEvent>| { |event: Box<dyn UnifiedEvent>| {
println!("🎉 Event received! Type: {:?}, ID: {}", event.event_type(), event.id());
match_event!(event, { match_event!(event, {
BonkPoolCreateEvent => |e: BonkPoolCreateEvent| { BonkPoolCreateEvent => |e: BonkPoolCreateEvent| {
// 使用grpc的时候,可以从每个事件中获取到block_time // 使用grpc的时候,可以从每个事件中获取到block_time
@@ -103,37 +119,37 @@ fn create_event_callback() -> impl Fn(Box<dyn UnifiedEvent>) {
println!("BonkPoolCreateEvent: {:?}", e.base_mint_param.symbol); println!("BonkPoolCreateEvent: {:?}", e.base_mint_param.symbol);
}, },
BonkTradeEvent => |e: BonkTradeEvent| { BonkTradeEvent => |e: BonkTradeEvent| {
println!("BonkTradeEvent: {:?}", e); println!("BonkTradeEvent: {e:?}");
}, },
PumpFunTradeEvent => |e: PumpFunTradeEvent| { PumpFunTradeEvent => |e: PumpFunTradeEvent| {
println!("PumpFunTradeEvent: {:?}", e); println!("PumpFunTradeEvent: {e:?}");
}, },
PumpFunCreateTokenEvent => |e: PumpFunCreateTokenEvent| { PumpFunCreateTokenEvent => |e: PumpFunCreateTokenEvent| {
println!("PumpFunCreateTokenEvent: {:?}", e); println!("PumpFunCreateTokenEvent: {e:?}");
}, },
PumpSwapBuyEvent => |e: PumpSwapBuyEvent| { PumpSwapBuyEvent => |e: PumpSwapBuyEvent| {
println!("Buy event: {:?}", e); println!("Buy event: {e:?}");
}, },
PumpSwapSellEvent => |e: PumpSwapSellEvent| { PumpSwapSellEvent => |e: PumpSwapSellEvent| {
println!("Sell event: {:?}", e); println!("Sell event: {e:?}");
}, },
PumpSwapCreatePoolEvent => |e: PumpSwapCreatePoolEvent| { PumpSwapCreatePoolEvent => |e: PumpSwapCreatePoolEvent| {
println!("CreatePool event: {:?}", e); println!("CreatePool event: {e:?}");
}, },
PumpSwapDepositEvent => |e: PumpSwapDepositEvent| { PumpSwapDepositEvent => |e: PumpSwapDepositEvent| {
println!("Deposit event: {:?}", e); println!("Deposit event: {e:?}");
}, },
PumpSwapWithdrawEvent => |e: PumpSwapWithdrawEvent| { PumpSwapWithdrawEvent => |e: PumpSwapWithdrawEvent| {
println!("Withdraw event: {:?}", e); println!("Withdraw event: {e:?}");
}, },
RaydiumCpmmSwapEvent => |e: RaydiumCpmmSwapEvent| { RaydiumCpmmSwapEvent => |e: RaydiumCpmmSwapEvent| {
println!("RaydiumCpmmSwapEvent: {:?}", e); println!("RaydiumCpmmSwapEvent: {e:?}");
}, },
RaydiumClmmSwapEvent => |e: RaydiumClmmSwapEvent| { RaydiumClmmSwapEvent => |e: RaydiumClmmSwapEvent| {
println!("RaydiumClmmSwapEvent: {:?}", e); println!("RaydiumClmmSwapEvent: {e:?}");
}, },
RaydiumClmmSwapV2Event => |e: RaydiumClmmSwapV2Event| { RaydiumClmmSwapV2Event => |e: RaydiumClmmSwapV2Event| {
println!("RaydiumClmmSwapV2Event: {:?}", e); println!("RaydiumClmmSwapV2Event: {e:?}");
} }
}); });
} }
+2 -2
View File
@@ -48,9 +48,9 @@ macro_rules! impl_unified_event {
} }
fn merge(&mut self, other: Box<dyn $crate::streaming::event_parser::core::traits::UnifiedEvent>) { 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>() { if let Some(_e) = other.as_any().downcast_ref::<$struct_name>() {
$( $(
self.$field = e.$field.clone(); self.$field = _e.$field.clone();
)* )*
} }
} }
+135 -10
View File
@@ -2,8 +2,80 @@ use borsh::{BorshDeserialize, BorshSerialize};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use solana_sdk::pubkey::Pubkey; use solana_sdk::pubkey::Pubkey;
use solana_transaction_status::UiInstruction; use solana_transaction_status::UiInstruction;
use std::collections::hash_map::DefaultHasher; use std::sync::Arc;
use std::hash::{Hash, Hasher}; use tokio::sync::Mutex;
// 对象池大小配置
const EVENT_METADATA_POOL_SIZE: usize = 1000;
const TRANSFER_DATA_POOL_SIZE: usize = 2000;
/// 事件元数据对象池
pub struct EventMetadataPool {
pool: Arc<Mutex<Vec<EventMetadata>>>,
}
impl Default for EventMetadataPool {
fn default() -> Self {
Self::new()
}
}
impl EventMetadataPool {
pub fn new() -> Self {
Self {
pool: Arc::new(Mutex::new(Vec::with_capacity(EVENT_METADATA_POOL_SIZE))),
}
}
pub async fn acquire(&self) -> Option<EventMetadata> {
let mut pool = self.pool.lock().await;
pool.pop()
}
pub async fn release(&self, metadata: EventMetadata) {
let mut pool = self.pool.lock().await;
if pool.len() < EVENT_METADATA_POOL_SIZE {
pool.push(metadata);
}
}
}
/// 传输数据对象池
pub struct TransferDataPool {
pool: Arc<Mutex<Vec<TransferData>>>,
}
impl Default for TransferDataPool {
fn default() -> Self {
Self::new()
}
}
impl TransferDataPool {
pub fn new() -> Self {
Self {
pool: Arc::new(Mutex::new(Vec::with_capacity(TRANSFER_DATA_POOL_SIZE))),
}
}
pub async fn acquire(&self) -> Option<TransferData> {
let mut pool = self.pool.lock().await;
pool.pop()
}
pub async fn release(&self, transfer_data: TransferData) {
let mut pool = self.pool.lock().await;
if pool.len() < TRANSFER_DATA_POOL_SIZE {
pool.push(transfer_data);
}
}
}
// 全局对象池实例
lazy_static::lazy_static! {
pub static ref EVENT_METADATA_POOL: EventMetadataPool = EventMetadataPool::new();
pub static ref TRANSFER_DATA_POOL: TransferDataPool = TransferDataPool::new();
}
#[derive( #[derive(
Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize, Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize,
@@ -57,6 +129,7 @@ pub enum EventType {
} }
impl EventType { impl EventType {
#[allow(clippy::inherent_to_string)]
pub fn to_string(&self) -> String { pub fn to_string(&self) -> String {
match self { match self {
EventType::PumpSwapBuy => "PumpSwapBuy".to_string(), EventType::PumpSwapBuy => "PumpSwapBuy".to_string(),
@@ -167,6 +240,7 @@ pub struct EventMetadata {
} }
impl EventMetadata { impl EventMetadata {
#[allow(clippy::too_many_arguments)]
pub fn new( pub fn new(
id: String, id: String,
signature: String, signature: String,
@@ -185,22 +259,73 @@ impl EventMetadata {
slot, slot,
block_time, block_time,
block_time_ms, block_time_ms,
program_received_time_ms: program_received_time_ms, program_received_time_ms,
program_handle_time_consuming_ms: 0, program_handle_time_consuming_ms: 0,
protocol, protocol,
event_type, event_type,
program_id, program_id,
transfer_datas: vec![], transfer_datas: Vec::with_capacity(4), // 预分配容量
index, index,
} }
} }
/// 使用对象池创建EventMetadata
#[allow(clippy::too_many_arguments)]
pub async fn new_with_pool(
id: String,
signature: String,
slot: u64,
block_time: i64,
block_time_ms: i64,
protocol: ProtocolType,
event_type: EventType,
program_id: Pubkey,
index: String,
program_received_time_ms: i64,
) -> Self {
// 尝试从对象池获取
if let Some(mut metadata) = EVENT_METADATA_POOL.acquire().await {
metadata.id = id;
metadata.signature = signature;
metadata.slot = slot;
metadata.block_time = block_time;
metadata.block_time_ms = block_time_ms;
metadata.program_received_time_ms = program_received_time_ms;
metadata.program_handle_time_consuming_ms = 0;
metadata.protocol = protocol;
metadata.event_type = event_type;
metadata.program_id = program_id;
metadata.index = index;
metadata.transfer_datas.clear();
return metadata;
}
// 如果对象池为空,创建新的
Self::new(
id,
signature,
slot,
block_time,
block_time_ms,
protocol,
event_type,
program_id,
index,
program_received_time_ms,
)
}
pub fn set_id(&mut self, id: String) { pub fn set_id(&mut self, id: String) {
let _id = format!("{}-{}-{}", self.signature, self.event_type.to_string(), id); self.id = id;
// 对传入的 id 进行哈希处理 }
let mut hasher = DefaultHasher::new();
_id.hash(&mut hasher); pub fn set_transfer_datas(&mut self, transfer_datas: Vec<TransferData>) {
let hash_value = hasher.finish(); self.transfer_datas = transfer_datas;
self.id = format!("{:x}", hash_value); }
/// 回收EventMetadata到对象池
pub async fn recycle(self) {
EVENT_METADATA_POOL.release(self).await;
} }
} }
+3 -2
View File
@@ -28,12 +28,13 @@ pub fn extract_discriminator(length: usize, data: &[u8]) -> Option<(&[u8], &[u8]
Some((&data[..length], &data[length..])) Some((&data[..length], &data[length..]))
} }
/// 检查鉴别器是否匹配 /// 检查鉴别器是否匹配 - 优化版本
pub fn discriminator_matches(data: &str, expected: &str) -> bool { pub fn discriminator_matches(data: &str, expected: &str) -> bool {
if data.len() < expected.len() { if data.len() < expected.len() {
return false; return false;
} }
&data[..expected.len()] == expected // 使用字节比较而不是字符串比较,更高效
data.as_bytes().starts_with(expected.as_bytes())
} }
/// 从日志中提取程序数据 /// 从日志中提取程序数据
+212 -99
View File
@@ -8,6 +8,8 @@ use solana_transaction_status::{
}; };
use std::fmt::Debug; use std::fmt::Debug;
use std::{collections::HashMap, str::FromStr}; use std::{collections::HashMap, str::FromStr};
use std::sync::Arc;
use tokio::sync::RwLock;
use crate::streaming::event_parser::common::{ use crate::streaming::event_parser::common::{
parse_transfer_datas_from_next_instructions, TransferData, parse_transfer_datas_from_next_instructions, TransferData,
@@ -20,6 +22,78 @@ use crate::streaming::event_parser::{
}, },
}; };
// 解析缓存配置
const PARSE_CACHE_SIZE: usize = 10000;
const CACHE_TTL_SECONDS: u64 = 300; // 5分钟
/// 解析结果缓存
#[derive(Clone)]
pub struct ParseCacheEntry {
pub events: Vec<Box<dyn UnifiedEvent>>,
pub timestamp: u64,
}
/// 事件解析缓存
pub struct EventParseCache {
cache: Arc<RwLock<HashMap<String, ParseCacheEntry>>>,
max_size: usize,
ttl_seconds: u64,
}
impl EventParseCache {
pub fn new(max_size: usize, ttl_seconds: u64) -> Self {
Self {
cache: Arc::new(RwLock::new(HashMap::with_capacity(max_size))),
max_size,
ttl_seconds,
}
}
pub async fn get(&self, key: &str) -> Option<Vec<Box<dyn UnifiedEvent>>> {
let cache = self.cache.read().await;
if let Some(entry) = cache.get(key) {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
if now - entry.timestamp < self.ttl_seconds {
return Some(entry.events.clone());
}
}
None
}
pub async fn set(&self, key: String, events: Vec<Box<dyn UnifiedEvent>>) {
let mut cache = self.cache.write().await;
// 清理过期条目
if cache.len() >= self.max_size {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
cache.retain(|_, entry| now - entry.timestamp < self.ttl_seconds);
}
let entry = ParseCacheEntry {
events,
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs(),
};
cache.insert(key, entry);
}
}
// 全局解析缓存实例
lazy_static::lazy_static! {
pub static ref PARSE_CACHE: EventParseCache = EventParseCache::new(PARSE_CACHE_SIZE, CACHE_TTL_SECONDS);
}
/// Unified Event Interface - All protocol events must implement this trait /// Unified Event Interface - All protocol events must implement this trait
pub trait UnifiedEvent: Debug + Send + Sync { pub trait UnifiedEvent: Debug + Send + Sync {
/// Get event ID /// Get event ID
@@ -68,9 +142,10 @@ pub trait UnifiedEvent: Debug + Send + Sync {
#[async_trait::async_trait] #[async_trait::async_trait]
pub trait EventParser: Send + Sync { pub trait EventParser: Send + Sync {
/// 从内联指令中解析事件数据 /// 从内联指令中解析事件数据
#[allow(clippy::too_many_arguments)]
fn parse_events_from_inner_instruction( fn parse_events_from_inner_instruction(
&self, &self,
instruction: &UiCompiledInstruction, inner_instruction: &UiCompiledInstruction,
signature: &str, signature: &str,
slot: u64, slot: u64,
block_time: Option<Timestamp>, block_time: Option<Timestamp>,
@@ -79,6 +154,7 @@ pub trait EventParser: Send + Sync {
) -> Vec<Box<dyn UnifiedEvent>>; ) -> Vec<Box<dyn UnifiedEvent>>;
/// 从指令中解析事件数据 /// 从指令中解析事件数据
#[allow(clippy::too_many_arguments)]
fn parse_events_from_instruction( fn parse_events_from_instruction(
&self, &self,
instruction: &CompiledInstruction, instruction: &CompiledInstruction,
@@ -91,9 +167,10 @@ pub trait EventParser: Send + Sync {
) -> Vec<Box<dyn UnifiedEvent>>; ) -> Vec<Box<dyn UnifiedEvent>>;
/// 从VersionedTransaction中解析指令事件的通用方法 /// 从VersionedTransaction中解析指令事件的通用方法
#[allow(clippy::too_many_arguments)]
async fn parse_instruction_events_from_versioned_transaction( async fn parse_instruction_events_from_versioned_transaction(
&self, &self,
versioned_tx: &VersionedTransaction, transaction: &VersionedTransaction,
signature: &str, signature: &str,
slot: Option<u64>, slot: Option<u64>,
block_time: Option<Timestamp>, block_time: Option<Timestamp>,
@@ -101,9 +178,10 @@ pub trait EventParser: Send + Sync {
accounts: &[Pubkey], accounts: &[Pubkey],
inner_instructions: &[UiInnerInstructions], inner_instructions: &[UiInnerInstructions],
) -> Result<Vec<Box<dyn UnifiedEvent>>> { ) -> Result<Vec<Box<dyn UnifiedEvent>>> {
let mut instruction_events = Vec::new(); // 预分配容量,避免动态扩容
let mut instruction_events = Vec::with_capacity(16);
// 获取交易的指令和账户 // 获取交易的指令和账户
let compiled_instructions = versioned_tx.message.instructions(); let compiled_instructions = transaction.message.instructions();
let mut accounts: Vec<Pubkey> = accounts.to_vec(); let mut accounts: Vec<Pubkey> = accounts.to_vec();
// 检查交易中是否包含程序 // 检查交易中是否包含程序
@@ -128,11 +206,11 @@ pub trait EventParser: Send + Sync {
slot, slot,
block_time, block_time,
program_received_time_ms, program_received_time_ms,
format!("{}", index), format!("{index}"),
) )
.await .await
{ {
if events.len() > 0 { if !events.is_empty() {
if let Some(inn) = if let Some(inn) =
inner_instructions.iter().find(|inner_instruction| { inner_instructions.iter().find(|inner_instruction| {
inner_instruction.index == index as u8 inner_instruction.index == index as u8
@@ -141,12 +219,12 @@ pub trait EventParser: Send + Sync {
events.iter_mut().for_each(|event| { events.iter_mut().for_each(|event| {
let transfer_datas = let transfer_datas =
parse_transfer_datas_from_next_instructions( parse_transfer_datas_from_next_instructions(
&inn, inn,
-1 as i8, -1_i8,
&accounts, &accounts,
event.event_type(), event.event_type(),
); );
event.set_transfer_datas(transfer_datas.clone()); event.set_transfer_datas(transfer_datas);
}); });
} }
instruction_events.extend(events); instruction_events.extend(events);
@@ -177,7 +255,7 @@ pub trait EventParser: Send + Sync {
block_time, block_time,
program_received_time_ms, program_received_time_ms,
&accounts, &accounts,
&vec![], &[],
) )
.await .await
.unwrap_or_else(|_e| vec![]); .unwrap_or_else(|_e| vec![]);
@@ -193,6 +271,14 @@ pub trait EventParser: Send + Sync {
program_received_time_ms: i64, program_received_time_ms: i64,
bot_wallet: Option<Pubkey>, bot_wallet: Option<Pubkey>,
) -> Result<Vec<Box<dyn UnifiedEvent>>> { ) -> Result<Vec<Box<dyn UnifiedEvent>>> {
// 生成缓存键
let cache_key = format!("{}_{}_{}", signature, slot.unwrap_or(0), program_received_time_ms);
// 尝试从缓存获取
if let Some(cached_events) = PARSE_CACHE.get(&cache_key).await {
return Ok(cached_events);
}
let transaction = tx.transaction; let transaction = tx.transaction;
// 检查交易元数据 // 检查交易元数据
let meta = tx let meta = tx
@@ -203,18 +289,27 @@ pub trait EventParser: Send + Sync {
let mut address_table_lookups: Vec<Pubkey> = vec![]; let mut address_table_lookups: Vec<Pubkey> = vec![];
let mut inner_instructions: Vec<UiInnerInstructions> = vec![]; let mut inner_instructions: Vec<UiInnerInstructions> = vec![];
if meta.err.is_none() { if meta.err.is_none() {
inner_instructions = meta.inner_instructions.as_ref().unwrap().clone(); // 正确处理OptionSerializer类型
let loaded_addresses = meta.loaded_addresses.as_ref().unwrap(); if let solana_transaction_status::option_serializer::OptionSerializer::Some(meta_inner_instructions) = &meta.inner_instructions {
for lookup in &loaded_addresses.writable { inner_instructions = meta_inner_instructions.clone();
address_table_lookups.push(Pubkey::from_str(lookup).unwrap());
} }
for lookup in &loaded_addresses.readonly { if let solana_transaction_status::option_serializer::OptionSerializer::Some(loaded_addresses) = &meta.loaded_addresses {
address_table_lookups.push(Pubkey::from_str(lookup).unwrap()); for lookup in &loaded_addresses.writable {
if let Ok(pubkey) = Pubkey::from_str(lookup) {
address_table_lookups.push(pubkey);
}
}
for lookup in &loaded_addresses.readonly {
if let Ok(pubkey) = Pubkey::from_str(lookup) {
address_table_lookups.push(pubkey);
}
}
} }
} }
let mut accounts: Vec<Pubkey> = vec![]; let mut accounts: Vec<Pubkey> = vec![];
let mut instruction_events = Vec::new(); // 预分配容量,避免动态扩容
let mut instruction_events = Vec::with_capacity(16);
// 解析指令事件 // 解析指令事件
if let Some(versioned_tx) = transaction.decode() { if let Some(versioned_tx) = transaction.decode() {
@@ -238,105 +333,101 @@ pub trait EventParser: Send + Sync {
} }
// 解析内联指令事件 // 解析内联指令事件
let mut inner_instruction_events = Vec::new(); // 预分配容量,避免动态扩容
let mut inner_instruction_events = Vec::with_capacity(8);
// 检查交易是否成功 // 检查交易是否成功
if meta.err.is_none() { if meta.err.is_none() {
for inner_instruction in inner_instructions { for inner_instruction in inner_instructions {
for (index, instruction) in inner_instruction.instructions.iter().enumerate() { for (index, instruction) in inner_instruction.instructions.iter().enumerate() {
match instruction { if let UiInstruction::Compiled(compiled) = instruction {
UiInstruction::Compiled(compiled) => { // 解析嵌套指令
// 解析嵌套指令 let compiled_instruction = CompiledInstruction {
let compiled_instruction = CompiledInstruction { program_id_index: compiled.program_id_index,
program_id_index: compiled.program_id_index, accounts: compiled.accounts.clone(),
accounts: compiled.accounts.clone(), data: bs58::decode(compiled.data.clone())
data: bs58::decode(compiled.data.clone()).into_vec().unwrap(), .into_vec()
}; .unwrap_or_else(|_| vec![]),
if let Ok(mut events) = self };
.parse_instruction( if let Ok(mut events) = self
&compiled_instruction, .parse_instruction(
&accounts, &compiled_instruction,
signature, &accounts,
slot, signature,
block_time, slot,
program_received_time_ms, block_time,
format!("{}.{}", inner_instruction.index, index), program_received_time_ms,
) format!("{index}"),
.await )
{ .await
if events.len() > 0 { {
events.iter_mut().for_each(|event| { if !events.is_empty() {
let transfer_datas = events.iter_mut().for_each(|event| {
parse_transfer_datas_from_next_instructions( let transfer_datas =
&inner_instruction, parse_transfer_datas_from_next_instructions(
index as i8, &inner_instruction,
&accounts, -1_i8,
event.event_type(), &accounts,
); event.event_type(),
event.set_transfer_datas(transfer_datas.clone()); );
}); event.set_transfer_datas(transfer_datas);
instruction_events.extend(events); });
} instruction_events.extend(events);
} }
if let Ok(mut events) = self }
.parse_inner_instruction( if let Ok(mut events) = self
compiled, .parse_inner_instruction(
signature, compiled,
slot, signature,
block_time, slot,
program_received_time_ms, block_time,
format!("{}.{}", inner_instruction.index, index), program_received_time_ms,
) format!("{}.{}", inner_instruction.index, index),
.await )
{ .await
if events.len() > 0 { {
events.iter_mut().for_each(|event| { if !events.is_empty() {
let transfer_datas = events.iter_mut().for_each(|event| {
parse_transfer_datas_from_next_instructions( let transfer_datas =
&inner_instruction, parse_transfer_datas_from_next_instructions(
index as i8, &inner_instruction,
&accounts, index as i8,
event.event_type(), &accounts,
); event.event_type(),
event.set_transfer_datas(transfer_datas.clone()); );
}); event.set_transfer_datas(transfer_datas);
inner_instruction_events.extend(events); });
} inner_instruction_events.extend(events);
} }
} }
_ => {}
} }
} }
} }
} }
if instruction_events.len() > 0 && inner_instruction_events.len() > 0 { if !instruction_events.is_empty() && !inner_instruction_events.is_empty() {
for instruction_event in &mut instruction_events { for instruction_event in &mut instruction_events {
for inner_instruction_event in &inner_instruction_events { for inner_instruction_event in &inner_instruction_events {
if instruction_event.id() == inner_instruction_event.id() { if instruction_event.id() == inner_instruction_event.id() {
let i_index = instruction_event.index(); let i_index = instruction_event.index();
let in_index = inner_instruction_event.index(); let in_index = inner_instruction_event.index();
if !i_index.contains(".") && in_index.contains(".") { if !i_index.contains(".") && in_index.contains(".") {
let in_index_parent_index = in_index.split(".").nth(0).unwrap(); let in_index_parts: Vec<&str> = in_index.split(".").collect();
if in_index_parent_index == i_index { if !in_index_parts.is_empty() && in_index_parts[0] == i_index {
instruction_event.merge(inner_instruction_event.clone_boxed()); instruction_event.merge(inner_instruction_event.clone_boxed());
break; break;
} }
} else if i_index.contains(".") && in_index.contains(".") { } else if i_index.contains(".") && in_index.contains(".") {
// 嵌套指令 // 嵌套指令
let i_index_parent_index = i_index.split(".").nth(0).unwrap(); let i_index_parts: Vec<&str> = i_index.split(".").collect();
let in_index_parent_index = in_index.split(".").nth(0).unwrap(); let in_index_parts: Vec<&str> = in_index.split(".").collect();
if i_index_parent_index == in_index_parent_index {
let i_index_child_index = i_index if !i_index_parts.is_empty() && !in_index_parts.is_empty()
.split(".") && i_index_parts[0] == in_index_parts[0] {
.nth(1) let i_index_child_index = i_index_parts.get(1)
.unwrap() .and_then(|s| s.parse::<u32>().ok())
.parse::<u32>()
.unwrap_or(0); .unwrap_or(0);
let in_index_child_index = in_index let in_index_child_index = in_index_parts.get(1)
.split(".") .and_then(|s| s.parse::<u32>().ok())
.nth(1)
.unwrap()
.parse::<u32>()
.unwrap_or(0); .unwrap_or(0);
if in_index_child_index > i_index_child_index { if in_index_child_index > i_index_child_index {
instruction_event.merge(inner_instruction_event.clone_boxed()); instruction_event.merge(inner_instruction_event.clone_boxed());
@@ -348,7 +439,13 @@ pub trait EventParser: Send + Sync {
} }
} }
} }
Ok(self.process_events(instruction_events, bot_wallet))
let result = self.process_events(instruction_events, bot_wallet);
// 缓存结果
PARSE_CACHE.set(cache_key, result.clone()).await;
Ok(result)
} }
fn process_events( fn process_events(
@@ -356,6 +453,7 @@ pub trait EventParser: Send + Sync {
mut events: Vec<Box<dyn UnifiedEvent>>, mut events: Vec<Box<dyn UnifiedEvent>>,
bot_wallet: Option<Pubkey>, bot_wallet: Option<Pubkey>,
) -> Vec<Box<dyn UnifiedEvent>> { ) -> Vec<Box<dyn UnifiedEvent>> {
let start_time = std::time::Instant::now();
let mut dev_address = vec![]; let mut dev_address = vec![];
let mut bonk_dev_address = None; let mut bonk_dev_address = None;
for event in &mut events { for event in &mut events {
@@ -391,6 +489,14 @@ pub trait EventParser: Send + Sync {
let now = chrono::Utc::now().timestamp_millis(); let now = chrono::Utc::now().timestamp_millis();
event.set_program_handle_time_consuming_ms(now - event.program_received_time_ms()); event.set_program_handle_time_consuming_ms(now - event.program_received_time_ms());
} }
// 记录处理时间
let processing_time = start_time.elapsed();
if processing_time.as_millis() > 10 {
log::warn!("Event processing took {}ms for {} events",
processing_time.as_millis(), events.len());
}
events events
} }
@@ -415,6 +521,7 @@ pub trait EventParser: Send + Sync {
Ok(events) Ok(events)
} }
#[allow(clippy::too_many_arguments)]
async fn parse_instruction( async fn parse_instruction(
&self, &self,
instruction: &CompiledInstruction, instruction: &CompiledInstruction,
@@ -485,17 +592,18 @@ impl GenericEventParser {
protocol_type: ProtocolType, protocol_type: ProtocolType,
configs: Vec<GenericEventParseConfig>, configs: Vec<GenericEventParseConfig>,
) -> Self { ) -> Self {
let mut inner_instruction_configs = HashMap::new(); // 预分配容量,避免动态扩容
let mut instruction_configs = HashMap::new(); let mut inner_instruction_configs = HashMap::with_capacity(configs.len());
let mut instruction_configs = HashMap::with_capacity(configs.len());
for config in configs { for config in configs {
inner_instruction_configs inner_instruction_configs
.entry(config.inner_instruction_discriminator) .entry(config.inner_instruction_discriminator)
.or_insert(vec![]) .or_insert_with(Vec::new)
.push(config.clone()); .push(config.clone());
instruction_configs instruction_configs
.entry(config.instruction_discriminator.to_vec()) .entry(config.instruction_discriminator.to_vec())
.or_insert(vec![]) .or_insert_with(Vec::new)
.push(config); .push(config);
} }
@@ -508,6 +616,7 @@ impl GenericEventParser {
} }
/// 通用的内联指令解析方法 /// 通用的内联指令解析方法
#[allow(clippy::too_many_arguments)]
fn parse_inner_instruction_event( fn parse_inner_instruction_event(
&self, &self,
config: &GenericEventParseConfig, config: &GenericEventParseConfig,
@@ -539,6 +648,7 @@ impl GenericEventParser {
} }
/// 通用的指令解析方法 /// 通用的指令解析方法
#[allow(clippy::too_many_arguments)]
fn parse_instruction_event( fn parse_instruction_event(
&self, &self,
config: &GenericEventParseConfig, config: &GenericEventParseConfig,
@@ -574,6 +684,7 @@ impl GenericEventParser {
#[async_trait::async_trait] #[async_trait::async_trait]
impl EventParser for GenericEventParser { impl EventParser for GenericEventParser {
/// 从内联指令中解析事件数据 /// 从内联指令中解析事件数据
#[allow(clippy::too_many_arguments)]
fn parse_events_from_inner_instruction( fn parse_events_from_inner_instruction(
&self, &self,
inner_instruction: &UiCompiledInstruction, inner_instruction: &UiCompiledInstruction,
@@ -584,8 +695,9 @@ impl EventParser for GenericEventParser {
index: String, index: String,
) -> Vec<Box<dyn UnifiedEvent>> { ) -> Vec<Box<dyn UnifiedEvent>> {
let inner_instruction_data = inner_instruction.data.clone(); let inner_instruction_data = inner_instruction.data.clone();
let inner_instruction_data_decoded = let inner_instruction_data_decoded = bs58::decode(inner_instruction_data)
bs58::decode(inner_instruction_data).into_vec().unwrap(); .into_vec()
.unwrap_or_else(|_| vec![]);
if inner_instruction_data_decoded.len() < 16 { if inner_instruction_data_decoded.len() < 16 {
return Vec::new(); return Vec::new();
} }
@@ -614,6 +726,7 @@ impl EventParser for GenericEventParser {
} }
/// 从指令中解析事件 /// 从指令中解析事件
#[allow(clippy::too_many_arguments)]
fn parse_events_from_instruction( fn parse_events_from_instruction(
&self, &self,
instruction: &CompiledInstruction, instruction: &CompiledInstruction,
+3 -2
View File
@@ -64,7 +64,8 @@ impl std::str::FromStr for Protocol {
} }
static EVENT_PARSERS: LazyLock<HashMap<Protocol, Arc<dyn EventParser>>> = LazyLock::new(|| { static EVENT_PARSERS: LazyLock<HashMap<Protocol, Arc<dyn EventParser>>> = LazyLock::new(|| {
let mut parsers: HashMap<Protocol, Arc<dyn EventParser>> = HashMap::new(); // 预分配容量,避免动态扩容
let mut parsers: HashMap<Protocol, Arc<dyn EventParser>> = HashMap::with_capacity(5);
parsers.insert(Protocol::PumpSwap, Arc::new(PumpSwapEventParser::new())); parsers.insert(Protocol::PumpSwap, Arc::new(PumpSwapEventParser::new()));
parsers.insert(Protocol::PumpFun, Arc::new(PumpFunEventParser::new())); parsers.insert(Protocol::PumpFun, Arc::new(PumpFunEventParser::new()));
parsers.insert(Protocol::Bonk, Arc::new(BonkEventParser::new())); parsers.insert(Protocol::Bonk, Arc::new(BonkEventParser::new()));
@@ -82,7 +83,7 @@ impl EventParserFactory {
/// 创建指定协议的事件解析器 /// 创建指定协议的事件解析器
pub fn create_parser(protocol: Protocol) -> Arc<dyn EventParser> { pub fn create_parser(protocol: Protocol) -> Arc<dyn EventParser> {
EVENT_PARSERS.get(&protocol).cloned().unwrap_or_else(|| { EVENT_PARSERS.get(&protocol).cloned().unwrap_or_else(|| {
panic!("Parser for protocol {} not found", protocol); panic!("Parser for protocol {protocol} not found");
}) })
} }
@@ -20,6 +20,12 @@ pub struct BonkEventParser {
inner: GenericEventParser, inner: GenericEventParser,
} }
impl Default for BonkEventParser {
fn default() -> Self {
Self::new()
}
}
impl BonkEventParser { impl BonkEventParser {
pub fn new() -> Self { pub fn new() -> Self {
// 配置所有事件类型 // 配置所有事件类型
@@ -73,9 +79,9 @@ impl BonkEventParser {
) -> Option<Box<dyn UnifiedEvent>> { ) -> Option<Box<dyn UnifiedEvent>> {
if let Ok(event) = borsh::from_slice::<BonkPoolCreateEvent>(data) { if let Ok(event) = borsh::from_slice::<BonkPoolCreateEvent>(data) {
let mut metadata = metadata; let mut metadata = metadata;
metadata.set_id(format!("{}", metadata.signature,)); metadata.set_id(metadata.signature.to_string());
Some(Box::new(BonkPoolCreateEvent { Some(Box::new(BonkPoolCreateEvent {
metadata: metadata, metadata,
..event ..event
})) }))
} else { } else {
@@ -93,7 +99,7 @@ impl BonkEventParser {
metadata.set_id(format!( metadata.set_id(format!(
"{}-{}", "{}-{}",
metadata.signature, metadata.signature,
event.pool_state.to_string() event.pool_state
)); ));
if metadata.event_type == EventType::BonkBuyExactIn if metadata.event_type == EventType::BonkBuyExactIn
|| metadata.event_type == EventType::BonkBuyExactOut || metadata.event_type == EventType::BonkBuyExactOut
@@ -101,15 +107,13 @@ impl BonkEventParser {
if event.trade_direction != TradeDirection::Buy { if event.trade_direction != TradeDirection::Buy {
return None; return None;
} }
} else if metadata.event_type == EventType::BonkSellExactIn } else if (metadata.event_type == EventType::BonkSellExactIn
|| metadata.event_type == EventType::BonkSellExactOut || metadata.event_type == EventType::BonkSellExactOut)
{ && event.trade_direction != TradeDirection::Sell {
if event.trade_direction != TradeDirection::Sell {
return None; return None;
} }
}
Some(Box::new(BonkTradeEvent { Some(Box::new(BonkTradeEvent {
metadata: metadata, metadata,
..event ..event
})) }))
} else { } else {
@@ -270,7 +274,7 @@ impl BonkEventParser {
let vesting_param = Self::parse_vesting_params(data, &mut offset)?; let vesting_param = Self::parse_vesting_params(data, &mut offset)?;
let mut metadata = metadata; let mut metadata = metadata;
metadata.set_id(format!("{}", metadata.signature)); metadata.set_id(metadata.signature.to_string());
Some(Box::new(BonkPoolCreateEvent { Some(Box::new(BonkPoolCreateEvent {
metadata, metadata,
@@ -17,6 +17,12 @@ pub struct PumpFunEventParser {
inner: GenericEventParser, inner: GenericEventParser,
} }
impl Default for PumpFunEventParser {
fn default() -> Self {
Self::new()
}
}
impl PumpFunEventParser { impl PumpFunEventParser {
pub fn new() -> Self { pub fn new() -> Self {
// 配置所有事件类型 // 配置所有事件类型
@@ -61,10 +67,10 @@ impl PumpFunEventParser {
metadata.signature, metadata.signature,
event.name, event.name,
event.symbol, event.symbol,
event.mint.to_string() event.mint
)); ));
Some(Box::new(PumpFunCreateTokenEvent { Some(Box::new(PumpFunCreateTokenEvent {
metadata: metadata, metadata,
..event ..event
})) }))
} else { } else {
@@ -82,12 +88,12 @@ impl PumpFunEventParser {
metadata.set_id(format!( metadata.set_id(format!(
"{}-{}-{}-{}", "{}-{}-{}-{}",
metadata.signature, metadata.signature,
event.mint.to_string(), event.mint,
event.user.to_string(), event.user,
event.is_buy.to_string() event.is_buy
)); ));
Some(Box::new(PumpFunTradeEvent { Some(Box::new(PumpFunTradeEvent {
metadata: metadata, metadata,
..event ..event
})) }))
} else { } else {
@@ -129,7 +135,7 @@ impl PumpFunEventParser {
metadata.signature, metadata.signature,
name, name,
symbol, symbol,
accounts[0].to_string() accounts[0]
)); ));
Some(Box::new(PumpFunCreateTokenEvent { Some(Box::new(PumpFunCreateTokenEvent {
@@ -162,9 +168,9 @@ impl PumpFunEventParser {
metadata.set_id(format!( metadata.set_id(format!(
"{}-{}-{}-{}", "{}-{}-{}-{}",
metadata.signature, metadata.signature,
accounts[2].to_string(), accounts[2],
accounts[6].to_string(), accounts[6],
true.to_string() true
)); ));
Some(Box::new(PumpFunTradeEvent { Some(Box::new(PumpFunTradeEvent {
metadata, metadata,
@@ -199,9 +205,9 @@ impl PumpFunEventParser {
metadata.set_id(format!( metadata.set_id(format!(
"{}-{}-{}-{}", "{}-{}-{}-{}",
metadata.signature, metadata.signature,
accounts[2].to_string(), accounts[2],
accounts[6].to_string(), accounts[6],
false.to_string() false
)); ));
Some(Box::new(PumpFunTradeEvent { Some(Box::new(PumpFunTradeEvent {
metadata, metadata,
@@ -267,6 +273,6 @@ impl EventParser for PumpFunEventParser {
} }
fn supported_program_ids(&self) -> Vec<Pubkey> { fn supported_program_ids(&self) -> Vec<Pubkey> {
self.inner.supported_program_ids() vec![PUMPFUN_PROGRAM_ID]
} }
} }
@@ -20,6 +20,12 @@ pub struct PumpSwapEventParser {
inner: GenericEventParser, inner: GenericEventParser,
} }
impl Default for PumpSwapEventParser {
fn default() -> Self {
Self::new()
}
}
impl PumpSwapEventParser { impl PumpSwapEventParser {
pub fn new() -> Self { pub fn new() -> Self {
// 配置所有事件类型 // 配置所有事件类型
@@ -78,7 +84,7 @@ impl PumpSwapEventParser {
metadata.signature, event.user, event.pool, event.base_amount_out metadata.signature, event.user, event.pool, event.base_amount_out
)); ));
Some(Box::new(PumpSwapBuyEvent { Some(Box::new(PumpSwapBuyEvent {
metadata: metadata, metadata,
..event ..event
})) }))
} else { } else {
@@ -98,7 +104,7 @@ impl PumpSwapEventParser {
metadata.signature, event.user, event.pool, event.base_amount_in metadata.signature, event.user, event.pool, event.base_amount_in
)); ));
Some(Box::new(PumpSwapSellEvent { Some(Box::new(PumpSwapSellEvent {
metadata: metadata, metadata,
..event ..event
})) }))
} else { } else {
@@ -118,7 +124,7 @@ impl PumpSwapEventParser {
metadata.signature, event.pool, event.creator, event.base_amount_in metadata.signature, event.pool, event.creator, event.base_amount_in
)); ));
Some(Box::new(PumpSwapCreatePoolEvent { Some(Box::new(PumpSwapCreatePoolEvent {
metadata: metadata, metadata,
..event ..event
})) }))
} else { } else {
@@ -138,7 +144,7 @@ impl PumpSwapEventParser {
metadata.signature, event.pool, event.user, event.lp_token_amount_out metadata.signature, event.pool, event.user, event.lp_token_amount_out
)); ));
Some(Box::new(PumpSwapDepositEvent { Some(Box::new(PumpSwapDepositEvent {
metadata: metadata, metadata,
..event ..event
})) }))
} else { } else {
@@ -158,7 +164,7 @@ impl PumpSwapEventParser {
metadata.signature, event.pool, event.user, event.lp_token_amount_in metadata.signature, event.pool, event.user, event.lp_token_amount_in
)); ));
Some(Box::new(PumpSwapWithdrawEvent { Some(Box::new(PumpSwapWithdrawEvent {
metadata: metadata, metadata,
..event ..event
})) }))
} else { } else {
@@ -413,6 +419,6 @@ impl EventParser for PumpSwapEventParser {
} }
fn supported_program_ids(&self) -> Vec<Pubkey> { fn supported_program_ids(&self) -> Vec<Pubkey> {
self.inner.supported_program_ids() vec![PUMPSWAP_PROGRAM_ID]
} }
} }
@@ -17,6 +17,12 @@ pub struct RaydiumClmmEventParser {
inner: GenericEventParser, inner: GenericEventParser,
} }
impl Default for RaydiumClmmEventParser {
fn default() -> Self {
Self::new()
}
}
impl RaydiumClmmEventParser { impl RaydiumClmmEventParser {
pub fn new() -> Self { pub fn new() -> Self {
// 配置所有事件类型 // 配置所有事件类型
@@ -89,7 +95,6 @@ impl RaydiumClmmEventParser {
token_program: accounts[8], token_program: accounts[8],
tick_array: accounts[9], tick_array: accounts[9],
remaining_accounts: accounts[10..].to_vec(), remaining_accounts: accounts[10..].to_vec(),
..Default::default()
})) }))
} }
@@ -133,7 +138,6 @@ impl RaydiumClmmEventParser {
input_vault_mint: accounts[11], input_vault_mint: accounts[11],
output_vault_mint: accounts[12], output_vault_mint: accounts[12],
remaining_accounts: accounts[13..].to_vec(), remaining_accounts: accounts[13..].to_vec(),
..Default::default()
})) }))
} }
} }
@@ -17,6 +17,12 @@ pub struct RaydiumCpmmEventParser {
inner: GenericEventParser, inner: GenericEventParser,
} }
impl Default for RaydiumCpmmEventParser {
fn default() -> Self {
Self::new()
}
}
impl RaydiumCpmmEventParser { impl RaydiumCpmmEventParser {
pub fn new() -> Self { pub fn new() -> Self {
// 配置所有事件类型 // 配置所有事件类型
+237 -13
View File
@@ -1,4 +1,5 @@
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::Mutex;
use futures::{channel::mpsc, StreamExt}; use futures::{channel::mpsc, StreamExt};
use solana_entry::entry::Entry; use solana_entry::entry::Entry;
@@ -14,10 +15,56 @@ use crate::protos::shredstream::shredstream_proxy_client::ShredstreamProxyClient
use crate::protos::shredstream::SubscribeEntriesRequest; use crate::protos::shredstream::SubscribeEntriesRequest;
use solana_sdk::pubkey::Pubkey; use solana_sdk::pubkey::Pubkey;
const CHANNEL_SIZE: usize = 1000; // 根据实际并发量调整通道大小,避免背压
const CHANNEL_SIZE: usize = 5000;
// 批处理配置
const SHRED_BATCH_SIZE: usize = 100;
#[allow(dead_code)]
const SHRED_BATCH_TIMEOUT_MS: u64 = 5;
/// ShredStream性能监控指标
#[derive(Debug, Clone)]
pub struct ShredPerformanceMetrics {
pub events_processed: u64,
pub events_per_second: f64,
pub average_processing_time_ms: f64,
pub min_processing_time_ms: f64,
pub max_processing_time_ms: f64,
pub memory_usage_mb: f64,
pub last_update_time: std::time::Instant,
pub events_in_window: u64,
pub window_start_time: std::time::Instant,
}
impl Default for ShredPerformanceMetrics {
fn default() -> Self {
Self::new()
}
}
impl ShredPerformanceMetrics {
pub fn new() -> Self {
let now = std::time::Instant::now();
Self {
events_processed: 0,
events_per_second: 0.0,
average_processing_time_ms: 0.0,
min_processing_time_ms: f64::MAX,
max_processing_time_ms: 0.0,
memory_usage_mb: 0.0,
last_update_time: now,
events_in_window: 0,
window_start_time: now,
}
}
}
#[derive(Clone)]
pub struct ShredStreamGrpc { pub struct ShredStreamGrpc {
shredstream_client: Arc<ShredstreamProxyClient<Channel>>, shredstream_client: Arc<ShredstreamProxyClient<Channel>>,
metrics: Arc<Mutex<ShredPerformanceMetrics>>,
enable_metrics: bool, // 是否启用性能监控
} }
struct TransactionWithSlot { struct TransactionWithSlot {
@@ -25,14 +72,151 @@ struct TransactionWithSlot {
slot: u64, slot: u64,
} }
/// ShredStream批处理器
pub struct ShredBatchProcessor<F>
where
F: FnMut(Vec<Box<dyn UnifiedEvent>>) + Send + Sync + 'static,
{
callback: F,
batch: Vec<Box<dyn UnifiedEvent>>,
batch_size: usize,
}
impl<F> ShredBatchProcessor<F>
where
F: FnMut(Vec<Box<dyn UnifiedEvent>>) + Send + Sync + 'static,
{
pub fn new(callback: F, batch_size: usize) -> Self {
Self {
callback,
batch: Vec::with_capacity(batch_size),
batch_size,
}
}
pub fn add_event(&mut self, event: Box<dyn UnifiedEvent>) {
self.batch.push(event);
if self.batch.len() >= self.batch_size {
self.flush();
}
}
pub fn flush(&mut self) {
if !self.batch.is_empty() {
let events = std::mem::replace(&mut self.batch, Vec::with_capacity(self.batch_size));
(self.callback)(events);
}
}
}
impl ShredStreamGrpc { impl ShredStreamGrpc {
pub async fn new(endpoint: String) -> AnyResult<Self> { pub async fn new(endpoint: String) -> AnyResult<Self> {
Self::new_with_config(endpoint, true).await
}
pub async fn new_with_config(endpoint: String, enable_metrics: bool) -> AnyResult<Self> {
let shredstream_client = ShredstreamProxyClient::connect(endpoint.clone()).await?; let shredstream_client = ShredstreamProxyClient::connect(endpoint.clone()).await?;
Ok(Self { Ok(Self {
shredstream_client: Arc::new(shredstream_client), shredstream_client: Arc::new(shredstream_client),
metrics: Arc::new(Mutex::new(ShredPerformanceMetrics::new())),
enable_metrics,
}) })
} }
/// 获取性能指标
pub async fn get_metrics(&self) -> ShredPerformanceMetrics {
let metrics = self.metrics.lock().await;
metrics.clone()
}
/// 启用或禁用性能监控
pub fn set_enable_metrics(&mut self, enabled: bool) {
self.enable_metrics = enabled;
}
/// 打印性能指标
pub async fn print_metrics(&self) {
let metrics = self.get_metrics().await;
println!("📊 ShredStream Performance Metrics:");
println!(" Events Processed: {}", metrics.events_processed);
println!(" Events/Second: {:.2}", metrics.events_per_second);
println!(" Avg Processing Time: {:.2}ms", metrics.average_processing_time_ms);
println!(" Min Processing Time: {:.2}ms", metrics.min_processing_time_ms);
println!(" Max Processing Time: {:.2}ms", metrics.max_processing_time_ms);
println!(" Memory Usage: {:.2}MB", metrics.memory_usage_mb);
println!("---");
}
/// 启动自动性能监控任务
pub async fn start_auto_metrics_monitoring(&self) {
// 检查是否启用性能监控
if !self.enable_metrics {
return; // 如果未启用性能监控,不启动监控任务
}
let grpc_clone = self.clone();
tokio::spawn(async move {
let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(10));
loop {
interval.tick().await;
grpc_clone.print_metrics().await;
}
});
}
/// 更新性能指标
async fn update_metrics(&self, events_processed: u64, processing_time_ms: f64) {
// 检查是否启用性能监控
if !self.enable_metrics {
return; // 如果未启用性能监控,直接返回
}
let mut metrics = self.metrics.lock().await;
let now = std::time::Instant::now();
metrics.events_processed += events_processed;
metrics.events_in_window += events_processed;
metrics.last_update_time = now;
// 更新最快和最慢处理时间
if processing_time_ms < metrics.min_processing_time_ms {
metrics.min_processing_time_ms = processing_time_ms;
}
if processing_time_ms > metrics.max_processing_time_ms {
metrics.max_processing_time_ms = processing_time_ms;
}
// 计算平均处理时间
if metrics.events_processed > 0 {
metrics.average_processing_time_ms =
(metrics.average_processing_time_ms * (metrics.events_processed - events_processed) as f64 + processing_time_ms)
/ metrics.events_processed as f64;
}
// 基于时间窗口计算每秒处理事件数(5秒窗口)
let window_duration = std::time::Duration::from_secs(5);
if now.duration_since(metrics.window_start_time) >= window_duration {
let window_seconds = now.duration_since(metrics.window_start_time).as_secs_f64();
if window_seconds > 0.0 && metrics.events_in_window > 0 {
metrics.events_per_second = metrics.events_in_window as f64 / window_seconds;
} else {
// 如果窗口内没有事件,保持之前的速率或设为0
metrics.events_per_second = 0.0;
}
// 重置窗口
metrics.events_in_window = 0;
metrics.window_start_time = now;
} else {
// 如果窗口还没满,不更新 events_per_second,保持之前的计算值
// 这样可以避免因为单次批处理时间波动导致的指标跳跃
}
// 估算内存使用(基于处理的事件数量)
metrics.memory_usage_mb = metrics.events_processed as f64 * 0.001; // 每个事件约1KB
}
pub async fn shredstream_subscribe<F>( pub async fn shredstream_subscribe<F>(
&self, &self,
protocols: Vec<Protocol>, protocols: Vec<Protocol>,
@@ -42,11 +226,23 @@ impl ShredStreamGrpc {
where where
F: Fn(Box<dyn UnifiedEvent>) + Send + Sync + 'static, F: Fn(Box<dyn UnifiedEvent>) + Send + Sync + 'static,
{ {
// 启动自动性能监控
self.start_auto_metrics_monitoring().await;
let request = tonic::Request::new(SubscribeEntriesRequest {}); let request = tonic::Request::new(SubscribeEntriesRequest {});
let mut client = (*self.shredstream_client).clone(); let mut client = (*self.shredstream_client).clone();
let mut stream = client.subscribe_entries(request).await?.into_inner(); let mut stream = client.subscribe_entries(request).await?.into_inner();
let (mut tx, mut rx) = mpsc::channel::<TransactionWithSlot>(CHANNEL_SIZE); let (mut tx, mut rx) = mpsc::channel::<TransactionWithSlot>(CHANNEL_SIZE);
let callback = Box::new(callback);
// 创建批处理器,将单个事件回调转换为批量回调
let batch_callback = move |events: Vec<Box<dyn UnifiedEvent>>| {
for event in events {
callback(event);
}
};
let mut batch_processor = ShredBatchProcessor::new(batch_callback, SHRED_BATCH_SIZE);
tokio::spawn(async move { tokio::spawn(async move {
while let Some(message) = stream.next().await { while let Some(message) = stream.next().await {
match message { match message {
@@ -70,36 +266,45 @@ impl ShredStreamGrpc {
} }
}); });
let self_clone = self.clone();
while let Some(transaction_with_slot) = rx.next().await { while let Some(transaction_with_slot) = rx.next().await {
if let Err(e) = Self::process_transaction( if let Err(e) = self_clone.process_transaction_with_batch(
transaction_with_slot, transaction_with_slot,
protocols.clone(), protocols.clone(),
bot_wallet, bot_wallet,
&*callback, &mut batch_processor,
) )
.await .await
{ {
error!("Error processing transaction: {:?}", e); error!("Error processing transaction: {e:?}");
} }
} }
// 处理剩余的事件
batch_processor.flush();
Ok(()) Ok(())
} }
async fn process_transaction<F>( async fn process_transaction_with_batch<F>(
&self,
transaction_with_slot: TransactionWithSlot, transaction_with_slot: TransactionWithSlot,
protocols: Vec<Protocol>, protocols: Vec<Protocol>,
bot_wallet: Option<Pubkey>, bot_wallet: Option<Pubkey>,
callback: &F, batch_processor: &mut ShredBatchProcessor<F>,
) -> AnyResult<()> ) -> AnyResult<()>
where where
F: Fn(Box<dyn UnifiedEvent>) + Send + Sync, F: FnMut(Vec<Box<dyn UnifiedEvent>>) + Send + Sync + 'static,
{ {
let start_time = std::time::Instant::now();
let program_received_time_ms = chrono::Utc::now().timestamp_millis(); let program_received_time_ms = chrono::Utc::now().timestamp_millis();
let slot = transaction_with_slot.slot; let slot = transaction_with_slot.slot;
let versioned_tx = transaction_with_slot.transaction; let versioned_tx = transaction_with_slot.transaction;
let signature = versioned_tx.signatures[0]; let signature = versioned_tx.signatures[0];
// 预分配向量容量
let mut all_events = Vec::with_capacity(protocols.len() * 2);
for protocol in protocols { for protocol in protocols {
let parser = EventParserFactory::create_parser(protocol.clone()); let parser = EventParserFactory::create_parser(protocol.clone());
let events = parser let events = parser
@@ -109,15 +314,34 @@ impl ShredStreamGrpc {
Some(slot), Some(slot),
None, None,
program_received_time_ms, program_received_time_ms,
bot_wallet.clone(), bot_wallet,
) )
.await .await
.unwrap_or_else(|_e| vec![]); .unwrap_or_else(|_e| vec![]);
for event in events { all_events.extend(events);
callback(event); }
}
// 保存事件数量用于日志记录
let event_count = all_events.len();
// 使用批处理器处理事件
for event in all_events {
batch_processor.add_event(event);
}
// 更新性能指标
let processing_time = start_time.elapsed();
let processing_time_ms = processing_time.as_millis() as f64;
// 实际调用性能指标更新
self.update_metrics(event_count as u64, processing_time_ms).await;
// 记录慢处理操作
if processing_time_ms > 5.0 {
log::warn!("ShredStream transaction processing took {}ms for {} events",
processing_time_ms, event_count);
} }
Ok(()) Ok(())
} }
} }
+443 -21
View File
@@ -14,6 +14,8 @@ use yellowstone_grpc_proto::geyser::{
SubscribeRequestFilterTransactions, SubscribeRequestPing, SubscribeUpdate, SubscribeRequestFilterTransactions, SubscribeRequestPing, SubscribeUpdate,
SubscribeUpdateTransaction, SubscribeUpdateTransaction,
}; };
use std::sync::Arc;
use tokio::sync::Mutex;
use crate::common::AnyResult; use crate::common::AnyResult;
use crate::streaming::event_parser::{EventParserFactory, Protocol, UnifiedEvent}; use crate::streaming::event_parser::{EventParserFactory, Protocol, UnifiedEvent};
@@ -22,9 +24,175 @@ type TransactionsFilterMap = HashMap<String, SubscribeRequestFilterTransactions>
const CONNECT_TIMEOUT: u64 = 10; const CONNECT_TIMEOUT: u64 = 10;
const REQUEST_TIMEOUT: u64 = 60; const REQUEST_TIMEOUT: u64 = 60;
const CHANNEL_SIZE: usize = 1000; // 根据实际并发量调整通道大小,避免背压
const CHANNEL_SIZE: usize = 5000;
const MAX_DECODING_MESSAGE_SIZE: usize = 1024 * 1024 * 10; const MAX_DECODING_MESSAGE_SIZE: usize = 1024 * 1024 * 10;
// 批处理配置
const BATCH_SIZE: usize = 100; // 批处理50个事件
const BATCH_TIMEOUT_MS: u64 = 10; // 减少超时时间到10ms
// 连接池配置(为将来扩展保留)
#[allow(dead_code)]
const CONNECTION_POOL_SIZE: usize = 5;
#[allow(dead_code)]
const CONNECTION_IDLE_TIMEOUT: Duration = Duration::from_secs(300); // 5分钟
// 工作线程池配置
const WORKER_THREADS: usize = 8;
const TASK_QUEUE_SIZE: usize = 10000;
/// 工作线程池配置
pub struct WorkerPoolConfig {
pub worker_threads: usize,
pub task_queue_size: usize,
}
impl Default for WorkerPoolConfig {
fn default() -> Self {
Self {
worker_threads: WORKER_THREADS,
task_queue_size: TASK_QUEUE_SIZE,
}
}
}
/// 性能监控指标
#[derive(Debug, Clone)]
pub struct PerformanceMetrics {
pub events_processed: u64,
pub events_per_second: f64,
pub average_processing_time_ms: f64,
pub min_processing_time_ms: f64,
pub max_processing_time_ms: f64,
pub cache_hit_rate: f64,
pub memory_usage_mb: f64,
pub last_update_time: std::time::Instant,
pub events_in_window: u64,
pub window_start_time: std::time::Instant,
}
impl Default for PerformanceMetrics {
fn default() -> Self {
Self::new()
}
}
impl PerformanceMetrics {
pub fn new() -> Self {
let now = std::time::Instant::now();
Self {
events_processed: 0,
events_per_second: 0.0,
average_processing_time_ms: 0.0,
min_processing_time_ms: f64::MAX,
max_processing_time_ms: 0.0,
cache_hit_rate: 0.0,
memory_usage_mb: 0.0,
last_update_time: now,
events_in_window: 0,
window_start_time: now,
}
}
}
/// gRPC连接池 - 简化版本
pub struct GrpcConnectionPool {
endpoint: String,
x_token: Option<String>,
}
impl GrpcConnectionPool {
pub fn new(endpoint: String, x_token: Option<String>) -> Self {
Self {
endpoint,
x_token,
}
}
pub async fn create_connection(&self) -> AnyResult<GeyserGrpcClient<impl Interceptor>> {
let builder = GeyserGrpcClient::build_from_shared(self.endpoint.clone())?
.x_token(self.x_token.clone())?
.tls_config(ClientTlsConfig::new().with_native_roots())?
.max_decoding_message_size(MAX_DECODING_MESSAGE_SIZE)
.connect_timeout(Duration::from_secs(CONNECT_TIMEOUT))
.timeout(Duration::from_secs(REQUEST_TIMEOUT));
Ok(builder.connect().await?)
}
}
/// 批处理事件收集器
pub struct EventBatchCollector<F>
where
F: Fn(Vec<Box<dyn UnifiedEvent>>) + Send + Sync + 'static,
{
callback: F,
batch: Vec<Box<dyn UnifiedEvent>>,
batch_size: usize,
timeout_ms: u64,
last_flush_time: std::time::Instant,
}
impl<F> EventBatchCollector<F>
where
F: Fn(Vec<Box<dyn UnifiedEvent>>) + Send + Sync + 'static,
{
pub fn new(callback: F, batch_size: usize, timeout_ms: u64) -> Self {
Self {
callback,
batch: Vec::with_capacity(batch_size),
batch_size,
timeout_ms,
last_flush_time: std::time::Instant::now(),
}
}
pub fn add_event(&mut self, event: Box<dyn UnifiedEvent>) {
log::debug!("Adding event to batch: {} (type: {:?})", event.id(), event.event_type());
self.batch.push(event);
// 检查是否需要刷新批次
if self.batch.len() >= self.batch_size || self.should_flush_by_timeout() {
log::info!("Flushing batch: size={}, timeout={}", self.batch.len(), self.should_flush_by_timeout());
self.flush();
}
}
pub fn flush(&mut self) {
if !self.batch.is_empty() {
let events = std::mem::replace(&mut self.batch, Vec::with_capacity(self.batch_size));
log::info!("Flushing {} events from batch processor", events.len());
// 添加更详细的调试信息
for (i, event) in events.iter().enumerate() {
log::info!("Event {}: Type={:?}, ID={}", i, event.event_type(), event.id());
}
// 执行回调并捕获可能的错误
log::info!("About to execute batch callback with {} events", events.len());
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
(self.callback)(events);
})) {
Ok(_) => {
log::info!("Batch callback executed successfully");
}
Err(e) => {
log::error!("Batch callback panicked: {:?}", e);
}
}
self.last_flush_time = std::time::Instant::now();
} else {
log::debug!("No events to flush");
}
}
fn should_flush_by_timeout(&self) -> bool {
self.last_flush_time.elapsed().as_millis() >= self.timeout_ms as u128
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct TransactionPretty { pub struct TransactionPretty {
pub slot: u64, pub slot: u64,
@@ -63,7 +231,7 @@ impl From<(SubscribeUpdateTransaction, Option<Timestamp>)> for TransactionPretty
let tx = transaction.expect("should be defined"); let tx = transaction.expect("should be defined");
Self { Self {
slot, slot,
block_time: block_time, block_time,
signature: Signature::try_from(tx.signature.as_slice()).expect("valid signature"), signature: Signature::try_from(tx.signature.as_slice()).expect("valid signature"),
is_vote: tx.is_vote, is_vote: tx.is_vote,
tx: yellowstone_grpc_proto::convert_from::create_tx_with_meta(tx) tx: yellowstone_grpc_proto::convert_from::create_tx_with_meta(tx)
@@ -74,20 +242,132 @@ impl From<(SubscribeUpdateTransaction, Option<Timestamp>)> for TransactionPretty
} }
} }
#[derive(Clone)]
pub struct YellowstoneGrpc { pub struct YellowstoneGrpc {
endpoint: String, endpoint: String,
x_token: Option<String>, x_token: Option<String>,
metrics: Arc<Mutex<PerformanceMetrics>>,
enable_metrics: bool, // 是否启用性能监控
} }
impl YellowstoneGrpc { impl YellowstoneGrpc {
pub fn new(endpoint: String, x_token: Option<String>) -> AnyResult<Self> { pub fn new(endpoint: String, x_token: Option<String>) -> AnyResult<Self> {
Self::new_with_config(endpoint, x_token, true)
}
pub fn new_with_config(
endpoint: String,
x_token: Option<String>,
enable_metrics: bool,
) -> AnyResult<Self> {
if CryptoProvider::get_default().is_none() { if CryptoProvider::get_default().is_none() {
default_provider() default_provider()
.install_default() .install_default()
.map_err(|e| anyhow::anyhow!("Failed to install crypto provider: {:?}", e))?; .map_err(|e| anyhow::anyhow!("Failed to install crypto provider: {:?}", e))?;
} }
Ok(Self { endpoint, x_token }) Ok(Self {
endpoint,
x_token,
metrics: Arc::new(Mutex::new(PerformanceMetrics::new())),
enable_metrics,
})
}
/// 获取性能指标
pub async fn get_metrics(&self) -> PerformanceMetrics {
let metrics = self.metrics.lock().await;
metrics.clone()
}
/// 启用或禁用性能监控
pub fn set_enable_metrics(&mut self, enabled: bool) {
self.enable_metrics = enabled;
}
/// 打印性能指标
pub async fn print_metrics(&self) {
let metrics = self.get_metrics().await;
println!("📊 Performance Metrics:");
println!(" Events Processed: {}", metrics.events_processed);
println!(" Events/Second: {:.2}", metrics.events_per_second);
println!(" Avg Processing Time: {:.2}ms", metrics.average_processing_time_ms);
println!(" Min Processing Time: {:.2}ms", metrics.min_processing_time_ms);
println!(" Max Processing Time: {:.2}ms", metrics.max_processing_time_ms);
println!(" Cache Hit Rate: {:.2}%", metrics.cache_hit_rate * 100.0);
println!(" Memory Usage: {:.2}MB", metrics.memory_usage_mb);
println!("---");
}
/// 启动自动性能监控任务
pub async fn start_auto_metrics_monitoring(&self) {
// 检查是否启用性能监控
if !self.enable_metrics {
return; // 如果未启用性能监控,不启动监控任务
}
let grpc_clone = self.clone();
tokio::spawn(async move {
let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(10));
loop {
interval.tick().await;
grpc_clone.print_metrics().await;
}
});
}
/// 更新性能指标
async fn update_metrics(&self, events_processed: u64, processing_time_ms: f64) {
// 检查是否启用性能监控
if !self.enable_metrics {
return; // 如果未启用性能监控,直接返回
}
let mut metrics = self.metrics.lock().await;
let now = std::time::Instant::now();
metrics.events_processed += events_processed;
metrics.events_in_window += events_processed;
metrics.last_update_time = now;
// 更新最快和最慢处理时间
if processing_time_ms < metrics.min_processing_time_ms {
metrics.min_processing_time_ms = processing_time_ms;
}
if processing_time_ms > metrics.max_processing_time_ms {
metrics.max_processing_time_ms = processing_time_ms;
}
// 计算平均处理时间
if metrics.events_processed > 0 {
metrics.average_processing_time_ms =
(metrics.average_processing_time_ms * (metrics.events_processed - events_processed) as f64 + processing_time_ms)
/ metrics.events_processed as f64;
}
// 基于时间窗口计算每秒处理事件数(5秒窗口)
let window_duration = std::time::Duration::from_secs(5);
if now.duration_since(metrics.window_start_time) >= window_duration {
let window_seconds = now.duration_since(metrics.window_start_time).as_secs_f64();
if window_seconds > 0.0 && metrics.events_in_window > 0 {
metrics.events_per_second = metrics.events_in_window as f64 / window_seconds;
} else {
// 如果窗口内没有事件,保持之前的速率或设为0
metrics.events_per_second = 0.0;
}
// 重置窗口
metrics.events_in_window = 0;
metrics.window_start_time = now;
} else {
// 如果窗口还没满,不更新 events_per_second,保持之前的计算值
// 这样可以避免因为单次批处理时间波动导致的指标跳跃
}
// 估算内存使用(基于处理的事件数量)
metrics.memory_usage_mb = metrics.events_processed as f64 * 0.001; // 每个事件约1KB
} }
pub async fn connect(&self) -> AnyResult<GeyserGrpcClient<impl Interceptor>> { pub async fn connect(&self) -> AnyResult<GeyserGrpcClient<impl Interceptor>> {
@@ -156,6 +436,7 @@ impl YellowstoneGrpc {
match msg.update_oneof { match msg.update_oneof {
Some(UpdateOneof::Transaction(sut)) => { Some(UpdateOneof::Transaction(sut)) => {
let transaction_pretty = TransactionPretty::from((sut, created_at)); let transaction_pretty = TransactionPretty::from((sut, created_at));
log::info!("Received transaction: {} at slot {}", transaction_pretty.signature, transaction_pretty.slot);
tx.try_send(transaction_pretty)?; tx.try_send(transaction_pretty)?;
} }
Some(UpdateOneof::Ping(_)) => { Some(UpdateOneof::Ping(_)) => {
@@ -170,7 +451,9 @@ impl YellowstoneGrpc {
Some(UpdateOneof::Pong(_)) => { Some(UpdateOneof::Pong(_)) => {
info!("service is pong: {}", Local::now()); info!("service is pong: {}", Local::now());
} }
_ => {} _ => {
log::debug!("Received other message type");
}
} }
Ok(()) Ok(())
} }
@@ -189,6 +472,7 @@ impl YellowstoneGrpc {
/// * `account_required` - List of account addresses that must be present in transactions /// * `account_required` - List of account addresses that must be present in transactions
/// * `commitment` - Optional commitment level for the subscription /// * `commitment` - Optional commitment level for the subscription
/// * `callback` - Function to call when matching events are found /// * `callback` - Function to call when matching events are found
#[allow(clippy::too_many_arguments)]
pub async fn subscribe_events_v2<F>( pub async fn subscribe_events_v2<F>(
&self, &self,
protocols: Vec<Protocol>, protocols: Vec<Protocol>,
@@ -202,6 +486,10 @@ impl YellowstoneGrpc {
where where
F: Fn(Box<dyn UnifiedEvent>) + Send + Sync + 'static, F: Fn(Box<dyn UnifiedEvent>) + Send + Sync + 'static,
{ {
// 启动自动性能监控
self.start_auto_metrics_monitoring().await;
if account_include.is_empty() && account_exclude.is_empty() && account_required.is_empty() { if account_include.is_empty() && account_exclude.is_empty() && account_required.is_empty() {
return Err(anyhow::anyhow!( return Err(anyhow::anyhow!(
"account_include or account_exclude or account_required cannot be empty" "account_include or account_exclude or account_required cannot be empty"
@@ -218,8 +506,14 @@ impl YellowstoneGrpc {
// Create channel // Create channel
let (mut tx, mut rx) = mpsc::channel::<TransactionPretty>(CHANNEL_SIZE); let (mut tx, mut rx) = mpsc::channel::<TransactionPretty>(CHANNEL_SIZE);
// Create callback function, wrap with Arc to share across multiple tasks // 创建批处理器,将单个事件回调转换为批量回调
let callback = std::sync::Arc::new(Box::new(callback)); let batch_callback = move |events: Vec<Box<dyn UnifiedEvent>>| {
for event in events {
callback(event);
}
};
let mut batch_processor = EventBatchCollector::new(batch_callback, BATCH_SIZE, BATCH_TIMEOUT_MS);
// Start task to process the stream // Start task to process the stream
tokio::spawn(async move { tokio::spawn(async move {
@@ -229,7 +523,7 @@ impl YellowstoneGrpc {
if let Err(e) = if let Err(e) =
Self::handle_stream_message(msg, &mut tx, &mut subscribe_tx).await Self::handle_stream_message(msg, &mut tx, &mut subscribe_tx).await
{ {
error!("Error handling message: {:?}", e); error!("Error handling message: {e:?}");
break; break;
} }
} }
@@ -241,20 +535,24 @@ impl YellowstoneGrpc {
} }
}); });
// Process transactions // Process transactions with batch processing
let self_clone = self.clone();
tokio::spawn(async move { tokio::spawn(async move {
while let Some(transaction_pretty) = rx.next().await { while let Some(transaction_pretty) = rx.next().await {
if let Err(e) = Self::process_event_transaction( if let Err(e) = self_clone.process_event_transaction_with_batch(
transaction_pretty, transaction_pretty,
&**callback, &mut batch_processor,
bot_wallet, bot_wallet,
protocols.clone(), protocols.clone(),
) )
.await .await
{ {
error!("Error processing transaction: {:?}", e); error!("Error processing transaction: {e:?}");
} }
} }
// 处理剩余的事件
batch_processor.flush();
}); });
tokio::signal::ctrl_c().await?; tokio::signal::ctrl_c().await?;
@@ -266,6 +564,7 @@ impl YellowstoneGrpc {
since = "0.1.5", since = "0.1.5",
note = "This method will be removed, please use the new API: subscribe_events_v2" note = "This method will be removed, please use the new API: subscribe_events_v2"
)] )]
#[allow(clippy::too_many_arguments)]
pub async fn subscribe_events<F>( pub async fn subscribe_events<F>(
&self, &self,
protocols: Vec<Protocol>, protocols: Vec<Protocol>,
@@ -282,8 +581,7 @@ impl YellowstoneGrpc {
// 创建过滤器 // 创建过滤器
let protocol_accounts = protocols let protocol_accounts = protocols
.iter() .iter()
.map(|p| p.get_program_id()) .flat_map(|p| p.get_program_id())
.flatten()
.map(|p| p.to_string()) .map(|p| p.to_string())
.collect::<Vec<String>>(); .collect::<Vec<String>>();
let mut account_include = account_include.unwrap_or_default(); let mut account_include = account_include.unwrap_or_default();
@@ -314,7 +612,7 @@ impl YellowstoneGrpc {
if let Err(e) = if let Err(e) =
Self::handle_stream_message(msg, &mut tx, &mut subscribe_tx).await Self::handle_stream_message(msg, &mut tx, &mut subscribe_tx).await
{ {
error!("Error handling message: {:?}", e); error!("Error handling message: {e:?}");
break; break;
} }
} }
@@ -337,7 +635,7 @@ impl YellowstoneGrpc {
) )
.await .await
{ {
error!("Error processing transaction: {:?}", e); error!("Error processing transaction: {e:?}");
} }
} }
}); });
@@ -355,15 +653,20 @@ impl YellowstoneGrpc {
where where
F: Fn(Box<dyn UnifiedEvent>) + Send + Sync, F: Fn(Box<dyn UnifiedEvent>) + Send + Sync,
{ {
let start_time = std::time::Instant::now();
let program_received_time_ms = chrono::Utc::now().timestamp_millis(); let program_received_time_ms = chrono::Utc::now().timestamp_millis();
let slot = transaction_pretty.slot; let slot = transaction_pretty.slot;
let signature = transaction_pretty.signature.to_string(); let signature = transaction_pretty.signature.to_string();
let mut futures = Vec::new();
// 预分配向量容量,避免动态扩容
let mut futures = Vec::with_capacity(protocols.len());
for protocol in protocols { for protocol in protocols {
let parser = EventParserFactory::create_parser(protocol); let parser = EventParserFactory::create_parser(protocol);
// 在异步任务中需要克隆值
let tx_clone = transaction_pretty.tx.clone(); let tx_clone = transaction_pretty.tx.clone();
let signature_clone = signature.clone(); let signature_clone = signature.clone();
let bot_wallet_clone = bot_wallet.clone(); let bot_wallet_clone = bot_wallet;
futures.push(tokio::spawn(async move { futures.push(tokio::spawn(async move {
parser parser
@@ -381,13 +684,132 @@ impl YellowstoneGrpc {
} }
let results = futures::future::join_all(futures).await; let results = futures::future::join_all(futures).await;
// 收集所有事件
let mut all_events = Vec::new();
for events in results.into_iter().flatten() {
all_events.extend(events);
}
// 保存事件数量用于日志记录
let event_count = all_events.len();
// 批量处理事件
if !all_events.is_empty() {
for event in all_events {
callback(event);
}
}
// 更新性能指标
let processing_time = start_time.elapsed();
let processing_time_ms = processing_time.as_millis() as f64;
// 记录慢处理操作
if processing_time_ms > 10.0 {
log::warn!("Slow event processing: {processing_time_ms}ms for {event_count} events");
}
Ok(())
}
async fn process_event_transaction_with_batch<F>(
&self,
transaction_pretty: TransactionPretty,
batch_processor: &mut EventBatchCollector<F>,
bot_wallet: Option<Pubkey>,
protocols: Vec<Protocol>,
) -> AnyResult<()>
where
F: Fn(Vec<Box<dyn UnifiedEvent>>) + Send + Sync + 'static,
{
let start_time = std::time::Instant::now();
let program_received_time_ms = chrono::Utc::now().timestamp_millis();
let slot = transaction_pretty.slot;
let signature = transaction_pretty.signature.to_string();
// 预分配向量容量,避免动态扩容
let mut futures: Vec<tokio::task::JoinHandle<Result<Vec<Box<dyn UnifiedEvent>>, anyhow::Error>>> = Vec::with_capacity(protocols.len());
for protocol in protocols {
let parser = EventParserFactory::create_parser(protocol.clone());
// 在异步任务中需要克隆值
let tx_clone = transaction_pretty.tx.clone();
let signature_clone = signature.clone();
let bot_wallet_clone = bot_wallet;
let protocol_clone = protocol.clone();
futures.push(tokio::spawn(async move {
let result = parser
.parse_transaction(
tx_clone,
&signature_clone,
Some(slot),
transaction_pretty.block_time,
program_received_time_ms,
bot_wallet_clone,
)
.await;
match result {
Ok(events) => {
if !events.is_empty() {
log::info!("Parsed {} events for protocol {:?}", events.len(), protocol_clone);
}
Ok(events)
}
Err(e) => {
log::warn!("Failed to parse transaction for protocol {:?}: {:?}", protocol_clone, e);
Ok(vec![])
}
}
}));
}
let results = futures::future::join_all(futures).await;
// 收集所有事件并使用批处理器
let mut total_events = 0;
for result in results { for result in results {
if let Ok(events) = result { match result {
for event in events { Ok(parse_result) => {
callback(event); match parse_result {
Ok(events) => {
total_events += events.len();
log::info!("Adding {} events to batch processor", events.len());
for event in events {
batch_processor.add_event(event);
}
}
Err(e) => {
log::warn!("Failed to parse transaction: {:?}", e);
}
}
}
Err(e) => {
log::warn!("Failed to get events from async task: {:?}", e);
} }
} }
} }
// 添加调试信息
if total_events > 0 {
log::info!("Total events parsed: {} for transaction {}", total_events, signature);
}
// 更新性能指标
let processing_time = start_time.elapsed();
let processing_time_ms = processing_time.as_millis() as f64;
// 实际调用性能指标更新
self.update_metrics(total_events as u64, processing_time_ms).await;
// 记录慢处理操作
if processing_time_ms > 10.0 {
log::warn!("Slow event processing: {processing_time_ms}ms for {total_events} events");
}
Ok(()) Ok(())
} }
} }
+4 -3
View File
@@ -9,7 +9,8 @@ use solana_sdk::{pubkey::Pubkey, transaction::VersionedTransaction};
use solana_transaction_status::EncodedTransactionWithStatusMeta; use solana_transaction_status::EncodedTransactionWithStatusMeta;
const SYSTEM_PROGRAM_ID: Pubkey = pubkey!("11111111111111111111111111111111"); const SYSTEM_PROGRAM_ID: Pubkey = pubkey!("11111111111111111111111111111111");
const CHANNEL_SIZE: usize = 1000; // 根据实际并发量调整通道大小,避免背压
const CHANNEL_SIZE: usize = 5000;
#[derive(Debug)] #[derive(Debug)]
pub enum SystemEvent { pub enum SystemEvent {
@@ -52,7 +53,7 @@ impl YellowstoneGrpc {
if let Err(e) = if let Err(e) =
Self::handle_stream_message(msg, &mut tx, &mut subscribe_tx).await Self::handle_stream_message(msg, &mut tx, &mut subscribe_tx).await
{ {
error!("Error handling message: {:?}", e); error!("Error handling message: {e:?}");
break; break;
} }
} }
@@ -66,7 +67,7 @@ impl YellowstoneGrpc {
while let Some(transaction_pretty) = rx.next().await { while let Some(transaction_pretty) = rx.next().await {
if let Err(e) = Self::process_system_transaction(transaction_pretty, &*callback).await { if let Err(e) = Self::process_system_transaction(transaction_pretty, &*callback).await {
error!("Error processing transaction: {:?}", e); error!("Error processing transaction: {e:?}");
} }
} }
Ok(()) Ok(())