0.7.27 +Refactor
This commit is contained in:
@@ -4,27 +4,27 @@
|
||||
|
||||
/// Persistence façade between technical detection and database storage.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct KbDetectionPersistenceService {
|
||||
pub struct DetectionPersistenceService {
|
||||
/// Shared database handle.
|
||||
database: std::sync::Arc<crate::KbDatabase>,
|
||||
database: std::sync::Arc<crate::Database>,
|
||||
}
|
||||
|
||||
impl KbDetectionPersistenceService {
|
||||
impl DetectionPersistenceService {
|
||||
/// Creates a new detection persistence service.
|
||||
pub fn new(database: std::sync::Arc<crate::KbDatabase>) -> Self {
|
||||
pub fn new(database: std::sync::Arc<crate::Database>) -> Self {
|
||||
return Self { database };
|
||||
}
|
||||
/// Returns the shared database handle.
|
||||
pub fn database(&self) -> &std::sync::Arc<crate::KbDatabase> {
|
||||
pub fn database(&self) -> &std::sync::Arc<crate::Database> {
|
||||
return &self.database;
|
||||
}
|
||||
|
||||
/// Persists one on-chain observation.
|
||||
pub async fn record_observation(
|
||||
&self,
|
||||
input: &crate::KbDetectionObservationInput,
|
||||
) -> Result<i64, crate::KbError> {
|
||||
let dto = crate::KbOnchainObservationDto::new(
|
||||
input: &crate::DetectionObservationInput,
|
||||
) -> Result<i64, crate::Error> {
|
||||
let dto = crate::OnchainObservationDto::new(
|
||||
input.observation_kind.clone(),
|
||||
input.source_kind,
|
||||
input.endpoint_name.clone(),
|
||||
@@ -32,15 +32,15 @@ impl KbDetectionPersistenceService {
|
||||
input.slot,
|
||||
input.payload.clone(),
|
||||
);
|
||||
return crate::insert_onchain_observation(self.database.as_ref(), &dto).await;
|
||||
return crate::query_onchain_observations_insert(self.database.as_ref(), &dto).await;
|
||||
}
|
||||
|
||||
/// Persists one analysis signal.
|
||||
pub async fn record_signal(
|
||||
&self,
|
||||
input: &crate::KbDetectionSignalInput,
|
||||
) -> Result<i64, crate::KbError> {
|
||||
let dto = crate::KbAnalysisSignalDto::new(
|
||||
input: &crate::DetectionSignalInput,
|
||||
) -> Result<i64, crate::Error> {
|
||||
let dto = crate::AnalysisSignalDto::new(
|
||||
input.signal_kind.clone(),
|
||||
input.severity,
|
||||
input.object_key.clone(),
|
||||
@@ -48,7 +48,7 @@ impl KbDetectionPersistenceService {
|
||||
input.score,
|
||||
input.payload.clone(),
|
||||
);
|
||||
return crate::insert_analysis_signal(self.database.as_ref(), &dto).await;
|
||||
return crate::query_analysis_signals_insert(self.database.as_ref(), &dto).await;
|
||||
}
|
||||
|
||||
/// Registers one token candidate from a technical source.
|
||||
@@ -59,9 +59,9 @@ impl KbDetectionPersistenceService {
|
||||
/// - stores the derived signal linked to that observation.
|
||||
pub async fn register_token_candidate(
|
||||
&self,
|
||||
input: &crate::KbDetectionTokenCandidateInput,
|
||||
) -> Result<crate::KbDetectionTokenCandidateResult, crate::KbError> {
|
||||
let token_dto = crate::KbTokenDto::new(
|
||||
input: &crate::DetectionTokenCandidateInput,
|
||||
) -> Result<crate::DetectionTokenCandidateResult, crate::Error> {
|
||||
let token_dto = crate::TokenDto::new(
|
||||
input.mint.clone(),
|
||||
input.symbol.clone(),
|
||||
input.name.clone(),
|
||||
@@ -69,12 +69,12 @@ impl KbDetectionPersistenceService {
|
||||
input.token_program.clone(),
|
||||
input.is_quote_token,
|
||||
);
|
||||
let token_id_result = crate::upsert_token(self.database.as_ref(), &token_dto).await;
|
||||
let token_id_result = crate::query_tokens_upsert(self.database.as_ref(), &token_dto).await;
|
||||
let token_id = match token_id_result {
|
||||
Ok(token_id) => token_id,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let observation_input = crate::KbDetectionObservationInput::new(
|
||||
let observation_input = crate::DetectionObservationInput::new(
|
||||
input.observation_kind.clone(),
|
||||
input.source_kind,
|
||||
input.endpoint_name.clone(),
|
||||
@@ -91,7 +91,7 @@ impl KbDetectionPersistenceService {
|
||||
Some(signal_payload) => signal_payload.clone(),
|
||||
None => input.observation_payload.clone(),
|
||||
};
|
||||
let signal_input = crate::KbDetectionSignalInput::new(
|
||||
let signal_input = crate::DetectionSignalInput::new(
|
||||
input.signal_kind.clone(),
|
||||
input.signal_severity,
|
||||
input.mint.clone(),
|
||||
@@ -104,7 +104,7 @@ impl KbDetectionPersistenceService {
|
||||
Ok(signal_id) => signal_id,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
return Ok(crate::KbDetectionTokenCandidateResult { token_id, observation_id, signal_id });
|
||||
return Ok(crate::DetectionTokenCandidateResult { token_id, observation_id, signal_id });
|
||||
}
|
||||
|
||||
/// Registers one pool candidate from a technical source.
|
||||
@@ -116,14 +116,14 @@ impl KbDetectionPersistenceService {
|
||||
/// - stores the technical observation and derived signal.
|
||||
pub async fn register_pool_candidate(
|
||||
&self,
|
||||
input: &crate::KbDetectionPoolCandidateInput,
|
||||
) -> Result<crate::KbDetectionPoolCandidateResult, crate::KbError> {
|
||||
let dexes_result = crate::list_dexes(self.database.as_ref()).await;
|
||||
input: &crate::DetectionPoolCandidateInput,
|
||||
) -> Result<crate::DetectionPoolCandidateResult, crate::Error> {
|
||||
let dexes_result = crate::query_dexs_list(self.database.as_ref()).await;
|
||||
let dexes = match dexes_result {
|
||||
Ok(dexes) => dexes,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let mut matched_dex_option: std::option::Option<crate::KbDexDto> = None;
|
||||
let mut matched_dex_option: std::option::Option<crate::DexDto> = None;
|
||||
for dex in dexes {
|
||||
let mut matched = false;
|
||||
if let Some(program_id) = &dex.program_id {
|
||||
@@ -146,7 +146,7 @@ impl KbDetectionPersistenceService {
|
||||
let matched_dex = match matched_dex_option {
|
||||
Some(matched_dex) => matched_dex,
|
||||
None => {
|
||||
return Err(crate::KbError::Db(format!(
|
||||
return Err(crate::Error::Db(format!(
|
||||
"cannot register pool candidate: no known dex matches program id '{}'",
|
||||
input.dex_program_id
|
||||
)));
|
||||
@@ -155,23 +155,23 @@ impl KbDetectionPersistenceService {
|
||||
let dex_id = match matched_dex.id {
|
||||
Some(dex_id) => dex_id,
|
||||
None => {
|
||||
return Err(crate::KbError::Db(
|
||||
return Err(crate::Error::Db(
|
||||
"cannot register pool candidate: matched dex has no id".to_string(),
|
||||
));
|
||||
},
|
||||
};
|
||||
let pool_dto = crate::KbPoolDto::new(
|
||||
let pool_dto = crate::PoolDto::new(
|
||||
dex_id,
|
||||
input.pool_address.clone(),
|
||||
crate::KbPoolKind::Unknown,
|
||||
crate::KbPoolStatus::Pending,
|
||||
crate::PoolKind::Unknown,
|
||||
crate::PoolStatus::Pending,
|
||||
);
|
||||
let pool_id_result = crate::upsert_pool(self.database.as_ref(), &pool_dto).await;
|
||||
let pool_id_result = crate::query_pools_upsert(self.database.as_ref(), &pool_dto).await;
|
||||
let pool_id = match pool_id_result {
|
||||
Ok(pool_id) => pool_id,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let listing_dto = crate::KbPoolListingDto::new(
|
||||
let listing_dto = crate::PoolListingDto::new(
|
||||
dex_id,
|
||||
pool_id,
|
||||
None,
|
||||
@@ -182,12 +182,12 @@ impl KbDetectionPersistenceService {
|
||||
None,
|
||||
);
|
||||
let pool_listing_id_result =
|
||||
crate::upsert_pool_listing(self.database.as_ref(), &listing_dto).await;
|
||||
crate::query_pool_listings_upsert(self.database.as_ref(), &listing_dto).await;
|
||||
let pool_listing_id = match pool_listing_id_result {
|
||||
Ok(pool_listing_id) => pool_listing_id,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let observation_input = crate::KbDetectionObservationInput::new(
|
||||
let observation_input = crate::DetectionObservationInput::new(
|
||||
input.observation_kind.clone(),
|
||||
input.source_kind,
|
||||
input.endpoint_name.clone(),
|
||||
@@ -204,7 +204,7 @@ impl KbDetectionPersistenceService {
|
||||
Some(signal_payload) => signal_payload.clone(),
|
||||
None => input.observation_payload.clone(),
|
||||
};
|
||||
let signal_input = crate::KbDetectionSignalInput::new(
|
||||
let signal_input = crate::DetectionSignalInput::new(
|
||||
input.signal_kind.clone(),
|
||||
input.signal_severity,
|
||||
input.pool_address.clone(),
|
||||
@@ -217,7 +217,7 @@ impl KbDetectionPersistenceService {
|
||||
Ok(signal_id) => signal_id,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
return Ok(crate::KbDetectionPoolCandidateResult {
|
||||
return Ok(crate::DetectionPoolCandidateResult {
|
||||
dex_id,
|
||||
pool_id,
|
||||
pool_listing_id,
|
||||
@@ -229,13 +229,13 @@ impl KbDetectionPersistenceService {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
async fn create_database() -> crate::KbDatabase {
|
||||
async fn create_database() -> crate::Database {
|
||||
let tempdir = tempfile::tempdir().expect("tempdir must succeed");
|
||||
let database_path = tempdir.path().join("detect_pipeline.sqlite3");
|
||||
let config = crate::KbDatabaseConfig {
|
||||
let config = crate::DatabaseConfig {
|
||||
enabled: true,
|
||||
backend: crate::KbDatabaseBackend::Sqlite,
|
||||
sqlite: crate::KbSqliteDatabaseConfig {
|
||||
backend: crate::DatabaseBackend::Sqlite,
|
||||
sqlite: crate::SqliteDatabaseConfig {
|
||||
path: database_path.to_string_lossy().to_string(),
|
||||
create_if_missing: true,
|
||||
busy_timeout_ms: 5000,
|
||||
@@ -244,7 +244,7 @@ mod tests {
|
||||
use_wal: true,
|
||||
},
|
||||
};
|
||||
return crate::KbDatabase::connect_and_initialize(&config)
|
||||
return crate::Database::connect_and_initialize(&config)
|
||||
.await
|
||||
.expect("database init must succeed");
|
||||
}
|
||||
@@ -252,26 +252,26 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn record_observation_and_signal_work() {
|
||||
let database = create_database().await;
|
||||
let service = crate::KbDetectionPersistenceService::new(std::sync::Arc::new(database));
|
||||
let service = crate::DetectionPersistenceService::new(std::sync::Arc::new(database));
|
||||
let observation_id = service
|
||||
.record_observation(&crate::KbDetectionObservationInput::new(
|
||||
.record_observation(&crate::DetectionObservationInput::new(
|
||||
"rpc.program_notification".to_string(),
|
||||
crate::KbObservationSourceKind::WsRpc,
|
||||
crate::ObservationSourceKind::WsRpc,
|
||||
Some("mainnet_public_ws_slots".to_string()),
|
||||
"So11111111111111111111111111111111111111112".to_string(),
|
||||
crate::WSOL_MINT_ID.to_string(),
|
||||
Some(123456),
|
||||
serde_json::json!({
|
||||
"mint": "So11111111111111111111111111111111111111112"
|
||||
"mint": crate::WSOL_MINT_ID
|
||||
}),
|
||||
))
|
||||
.await
|
||||
.expect("record observation must succeed");
|
||||
assert!(observation_id > 0);
|
||||
let signal_id = service
|
||||
.record_signal(&crate::KbDetectionSignalInput::new(
|
||||
.record_signal(&crate::DetectionSignalInput::new(
|
||||
"rpc.token_candidate".to_string(),
|
||||
crate::KbAnalysisSignalSeverity::Low,
|
||||
"So11111111111111111111111111111111111111112".to_string(),
|
||||
crate::AnalysisSignalSeverity::Low,
|
||||
crate::WSOL_MINT_ID.to_string(),
|
||||
Some(observation_id),
|
||||
Some(0.25),
|
||||
serde_json::json!({
|
||||
@@ -281,31 +281,32 @@ mod tests {
|
||||
.await
|
||||
.expect("record signal must succeed");
|
||||
assert!(signal_id > 0);
|
||||
let observations = crate::list_recent_onchain_observations(service.database().as_ref(), 10)
|
||||
.await
|
||||
.expect("list observations must succeed");
|
||||
let signals = crate::list_recent_analysis_signals(service.database().as_ref(), 10)
|
||||
let observations =
|
||||
crate::query_onchain_observations_list_recent(service.database().as_ref(), 10)
|
||||
.await
|
||||
.expect("list observations must succeed");
|
||||
let signals = crate::query_analysis_signals_list(service.database().as_ref(), 10)
|
||||
.await
|
||||
.expect("list signals must succeed");
|
||||
assert_eq!(observations.len(), 1);
|
||||
assert_eq!(signals.len(), 1);
|
||||
assert_eq!(observations[0].object_key, "So11111111111111111111111111111111111111112");
|
||||
assert_eq!(signals[0].object_key, "So11111111111111111111111111111111111111112");
|
||||
assert_eq!(observations[0].object_key, crate::WSOL_MINT_ID);
|
||||
assert_eq!(signals[0].object_key, crate::WSOL_MINT_ID);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn register_token_candidate_persists_token_observation_and_signal() {
|
||||
let database = create_database().await;
|
||||
let service = crate::KbDetectionPersistenceService::new(std::sync::Arc::new(database));
|
||||
let service = crate::DetectionPersistenceService::new(std::sync::Arc::new(database));
|
||||
let result = service
|
||||
.register_token_candidate(&crate::KbDetectionTokenCandidateInput::new(
|
||||
.register_token_candidate(&crate::DetectionTokenCandidateInput::new(
|
||||
"Mint111111111111111111111111111111111111111".to_string(),
|
||||
Some("TEST".to_string()),
|
||||
Some("Test Token".to_string()),
|
||||
Some(6),
|
||||
crate::SPL_TOKEN_PROGRAM_ID.to_string(),
|
||||
false,
|
||||
crate::KbObservationSourceKind::WsRpc,
|
||||
crate::ObservationSourceKind::WsRpc,
|
||||
Some("helius_primary_ws_programs".to_string()),
|
||||
Some(777777),
|
||||
"rpc.token_candidate".to_string(),
|
||||
@@ -314,7 +315,7 @@ mod tests {
|
||||
"source": "ws"
|
||||
}),
|
||||
"signal.token_candidate".to_string(),
|
||||
crate::KbAnalysisSignalSeverity::Medium,
|
||||
crate::AnalysisSignalSeverity::Medium,
|
||||
Some(0.70),
|
||||
None,
|
||||
))
|
||||
@@ -323,7 +324,7 @@ mod tests {
|
||||
assert!(result.token_id > 0);
|
||||
assert!(result.observation_id > 0);
|
||||
assert!(result.signal_id > 0);
|
||||
let token = crate::get_token_by_mint(
|
||||
let token = crate::query_tokens_get_by_mint(
|
||||
service.database().as_ref(),
|
||||
"Mint111111111111111111111111111111111111111",
|
||||
)
|
||||
@@ -331,10 +332,11 @@ mod tests {
|
||||
.expect("get token must succeed");
|
||||
assert!(token.is_some());
|
||||
assert_eq!(token.expect("token must exist").symbol.as_deref(), Some("TEST"));
|
||||
let observations = crate::list_recent_onchain_observations(service.database().as_ref(), 10)
|
||||
.await
|
||||
.expect("list observations must succeed");
|
||||
let signals = crate::list_recent_analysis_signals(service.database().as_ref(), 10)
|
||||
let observations =
|
||||
crate::query_onchain_observations_list_recent(service.database().as_ref(), 10)
|
||||
.await
|
||||
.expect("list observations must succeed");
|
||||
let signals = crate::query_analysis_signals_list(service.database().as_ref(), 10)
|
||||
.await
|
||||
.expect("list signals must succeed");
|
||||
assert_eq!(observations.len(), 1);
|
||||
@@ -347,10 +349,10 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn register_pool_candidate_persists_pool_listing_observation_and_signal() {
|
||||
let database = create_database().await;
|
||||
let service = crate::KbDetectionPersistenceService::new(std::sync::Arc::new(database));
|
||||
let dex_id = crate::upsert_dex(
|
||||
let service = crate::DetectionPersistenceService::new(std::sync::Arc::new(database));
|
||||
let dex_id = crate::query_dexs_upsert(
|
||||
service.database().as_ref(),
|
||||
&crate::KbDexDto::new(
|
||||
&crate::DexDto::new(
|
||||
"raydium".to_string(),
|
||||
"Raydium".to_string(),
|
||||
Some("DexProgram111111111111111111111111111111111".to_string()),
|
||||
@@ -362,10 +364,10 @@ mod tests {
|
||||
.expect("upsert dex must succeed");
|
||||
assert!(dex_id > 0);
|
||||
let result = service
|
||||
.register_pool_candidate(&crate::KbDetectionPoolCandidateInput::new(
|
||||
.register_pool_candidate(&crate::DetectionPoolCandidateInput::new(
|
||||
"Pool111111111111111111111111111111111111111".to_string(),
|
||||
"DexProgram111111111111111111111111111111111".to_string(),
|
||||
crate::KbObservationSourceKind::WsRpc,
|
||||
crate::ObservationSourceKind::WsRpc,
|
||||
Some("helius_primary_ws_programs".to_string()),
|
||||
Some(999999),
|
||||
"ws.program_notification".to_string(),
|
||||
@@ -373,7 +375,7 @@ mod tests {
|
||||
"pool": "Pool111111111111111111111111111111111111111"
|
||||
}),
|
||||
"signal.pool_candidate.raydium".to_string(),
|
||||
crate::KbAnalysisSignalSeverity::Medium,
|
||||
crate::AnalysisSignalSeverity::Medium,
|
||||
None,
|
||||
None,
|
||||
))
|
||||
@@ -384,7 +386,7 @@ mod tests {
|
||||
assert!(result.pool_listing_id > 0);
|
||||
assert!(result.observation_id > 0);
|
||||
assert!(result.signal_id > 0);
|
||||
let pool = crate::get_pool_by_address(
|
||||
let pool = crate::query_pools_get_by_address(
|
||||
service.database().as_ref(),
|
||||
"Pool111111111111111111111111111111111111111",
|
||||
)
|
||||
@@ -392,9 +394,9 @@ mod tests {
|
||||
.expect("get pool must succeed");
|
||||
assert!(pool.is_some());
|
||||
let pool = pool.expect("pool must exist");
|
||||
assert_eq!(pool.status, crate::KbPoolStatus::Pending);
|
||||
assert_eq!(pool.status, crate::PoolStatus::Pending);
|
||||
let listing =
|
||||
crate::get_pool_listing_by_pool_id(service.database().as_ref(), result.pool_id)
|
||||
crate::query_pool_listings_get_by_pool_id(service.database().as_ref(), result.pool_id)
|
||||
.await
|
||||
.expect("get listing must succeed");
|
||||
assert!(listing.is_some());
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
/// Result of one Solana WebSocket detection pass.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub enum KbSolanaWsDetectionOutcome {
|
||||
pub enum SolanaWsDetectionOutcome {
|
||||
/// The notification is currently ignored by the detector.
|
||||
Ignored,
|
||||
/// A technical observation was stored.
|
||||
@@ -18,30 +18,30 @@ pub enum KbSolanaWsDetectionOutcome {
|
||||
/// A token candidate was registered.
|
||||
TokenCandidateRegistered {
|
||||
/// Persistence result.
|
||||
result: crate::KbDetectionTokenCandidateResult,
|
||||
result: crate::DetectionTokenCandidateResult,
|
||||
},
|
||||
/// A pool candidate was registered.
|
||||
PoolCandidateRegistered {
|
||||
/// Persistence result.
|
||||
result: crate::KbDetectionPoolCandidateResult,
|
||||
result: crate::DetectionPoolCandidateResult,
|
||||
},
|
||||
}
|
||||
|
||||
/// Detection service for Solana WebSocket notifications.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct KbSolanaWsDetectionService {
|
||||
pub struct SolanaWsDetectionService {
|
||||
/// Shared persistence façade.
|
||||
persistence: crate::KbDetectionPersistenceService,
|
||||
persistence: crate::DetectionPersistenceService,
|
||||
}
|
||||
|
||||
impl KbSolanaWsDetectionService {
|
||||
impl SolanaWsDetectionService {
|
||||
/// Creates a new Solana WebSocket detection service.
|
||||
pub fn new(persistence: crate::KbDetectionPersistenceService) -> Self {
|
||||
pub fn new(persistence: crate::DetectionPersistenceService) -> Self {
|
||||
return Self { persistence };
|
||||
}
|
||||
|
||||
/// Returns the shared persistence façade.
|
||||
pub fn persistence(&self) -> &crate::KbDetectionPersistenceService {
|
||||
pub fn persistence(&self) -> &crate::DetectionPersistenceService {
|
||||
return &self.persistence;
|
||||
}
|
||||
|
||||
@@ -49,19 +49,19 @@ impl KbSolanaWsDetectionService {
|
||||
pub async fn process_notification(
|
||||
&self,
|
||||
endpoint_name: std::option::Option<std::string::String>,
|
||||
notification: &crate::KbJsonRpcWsNotification,
|
||||
) -> Result<crate::KbSolanaWsDetectionOutcome, crate::KbError> {
|
||||
notification: &crate::JsonRpcWsNotification,
|
||||
) -> Result<crate::SolanaWsDetectionOutcome, crate::Error> {
|
||||
let observation_kind_option =
|
||||
map_notification_method_to_observation_kind(notification.method.as_str());
|
||||
let observation_kind = match observation_kind_option {
|
||||
Some(observation_kind) => observation_kind,
|
||||
None => return Ok(crate::KbSolanaWsDetectionOutcome::Ignored),
|
||||
None => return Ok(crate::SolanaWsDetectionOutcome::Ignored),
|
||||
};
|
||||
let token_candidate_result =
|
||||
self.try_register_token_candidate(endpoint_name.clone(), notification).await;
|
||||
match token_candidate_result {
|
||||
Ok(Some(result)) => {
|
||||
return Ok(crate::KbSolanaWsDetectionOutcome::TokenCandidateRegistered { result });
|
||||
return Ok(crate::SolanaWsDetectionOutcome::TokenCandidateRegistered { result });
|
||||
},
|
||||
Ok(None) => {},
|
||||
Err(error) => return Err(error),
|
||||
@@ -70,7 +70,7 @@ impl KbSolanaWsDetectionService {
|
||||
self.try_register_pool_candidate(endpoint_name.clone(), notification).await;
|
||||
match pool_candidate_result {
|
||||
Ok(Some(result)) => {
|
||||
return Ok(crate::KbSolanaWsDetectionOutcome::PoolCandidateRegistered { result });
|
||||
return Ok(crate::SolanaWsDetectionOutcome::PoolCandidateRegistered { result });
|
||||
},
|
||||
Ok(None) => {},
|
||||
Err(error) => return Err(error),
|
||||
@@ -83,9 +83,9 @@ impl KbSolanaWsDetectionService {
|
||||
);
|
||||
let slot =
|
||||
extract_slot_from_result(notification.method.as_str(), ¬ification.params.result);
|
||||
let observation_input = crate::KbDetectionObservationInput::new(
|
||||
let observation_input = crate::DetectionObservationInput::new(
|
||||
observation_kind,
|
||||
crate::KbObservationSourceKind::WsRpc,
|
||||
crate::ObservationSourceKind::WsRpc,
|
||||
endpoint_name.clone(),
|
||||
object_key.clone(),
|
||||
slot,
|
||||
@@ -103,7 +103,7 @@ impl KbSolanaWsDetectionService {
|
||||
_ => false,
|
||||
};
|
||||
if should_emit_signal {
|
||||
let signal_input = crate::KbDetectionSignalInput::new(
|
||||
let signal_input = crate::DetectionSignalInput::new(
|
||||
build_signal_kind_for_notification(
|
||||
notification.method.as_str(),
|
||||
¬ification.params.result,
|
||||
@@ -122,15 +122,15 @@ impl KbSolanaWsDetectionService {
|
||||
return Err(error);
|
||||
}
|
||||
}
|
||||
return Ok(crate::KbSolanaWsDetectionOutcome::ObservationRecorded { observation_id });
|
||||
return Ok(crate::SolanaWsDetectionOutcome::ObservationRecorded { observation_id });
|
||||
}
|
||||
|
||||
/// Tries to register a token candidate from one notification.
|
||||
async fn try_register_token_candidate(
|
||||
&self,
|
||||
endpoint_name: std::option::Option<std::string::String>,
|
||||
notification: &crate::KbJsonRpcWsNotification,
|
||||
) -> Result<std::option::Option<crate::KbDetectionTokenCandidateResult>, crate::KbError> {
|
||||
notification: &crate::JsonRpcWsNotification,
|
||||
) -> Result<std::option::Option<crate::DetectionTokenCandidateResult>, crate::Error> {
|
||||
if notification.method.as_str() != "programNotification" {
|
||||
return Ok(None);
|
||||
}
|
||||
@@ -182,14 +182,14 @@ impl KbSolanaWsDetectionService {
|
||||
extract_slot_from_result(notification.method.as_str(), ¬ification.params.result);
|
||||
let payload = build_notification_payload(notification);
|
||||
let is_quote_token = mint == crate::WSOL_MINT_ID.to_string();
|
||||
let input = crate::KbDetectionTokenCandidateInput::new(
|
||||
let input = crate::DetectionTokenCandidateInput::new(
|
||||
mint,
|
||||
None,
|
||||
None,
|
||||
decimals,
|
||||
token_program,
|
||||
is_quote_token,
|
||||
crate::KbObservationSourceKind::WsRpc,
|
||||
crate::ObservationSourceKind::WsRpc,
|
||||
endpoint_name,
|
||||
slot,
|
||||
"ws.program_notification".to_string(),
|
||||
@@ -199,7 +199,7 @@ impl KbSolanaWsDetectionService {
|
||||
} else {
|
||||
"signal.token_account_detected".to_string()
|
||||
},
|
||||
crate::KbAnalysisSignalSeverity::Medium,
|
||||
crate::AnalysisSignalSeverity::Medium,
|
||||
None,
|
||||
Some(payload),
|
||||
);
|
||||
@@ -214,8 +214,8 @@ impl KbSolanaWsDetectionService {
|
||||
async fn try_register_pool_candidate(
|
||||
&self,
|
||||
endpoint_name: std::option::Option<std::string::String>,
|
||||
notification: &crate::KbJsonRpcWsNotification,
|
||||
) -> Result<std::option::Option<crate::KbDetectionPoolCandidateResult>, crate::KbError> {
|
||||
notification: &crate::JsonRpcWsNotification,
|
||||
) -> Result<std::option::Option<crate::DetectionPoolCandidateResult>, crate::Error> {
|
||||
if notification.method.as_str() != "programNotification" {
|
||||
return Ok(None);
|
||||
}
|
||||
@@ -235,12 +235,12 @@ impl KbSolanaWsDetectionService {
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
let dexes_result = crate::list_dexes(self.persistence.database().as_ref()).await;
|
||||
let dexes_result = crate::query_dexs_list(self.persistence.database().as_ref()).await;
|
||||
let dexes = match dexes_result {
|
||||
Ok(dexes) => dexes,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let mut matched_dex_option: std::option::Option<crate::KbDexDto> = None;
|
||||
let mut matched_dex_option: std::option::Option<crate::DexDto> = None;
|
||||
for dex in dexes {
|
||||
let mut matched = false;
|
||||
if let Some(program_id) = &dex.program_id {
|
||||
@@ -268,16 +268,16 @@ impl KbSolanaWsDetectionService {
|
||||
let slot =
|
||||
extract_slot_from_result(notification.method.as_str(), ¬ification.params.result);
|
||||
let signal_kind = format!("signal.pool_candidate.{}", matched_dex.code);
|
||||
let input = crate::KbDetectionPoolCandidateInput::new(
|
||||
let input = crate::DetectionPoolCandidateInput::new(
|
||||
pubkey,
|
||||
owner,
|
||||
crate::KbObservationSourceKind::WsRpc,
|
||||
crate::ObservationSourceKind::WsRpc,
|
||||
endpoint_name,
|
||||
slot,
|
||||
"ws.program_notification".to_string(),
|
||||
payload.clone(),
|
||||
signal_kind,
|
||||
crate::KbAnalysisSignalSeverity::Medium,
|
||||
crate::AnalysisSignalSeverity::Medium,
|
||||
None,
|
||||
Some(payload),
|
||||
);
|
||||
@@ -308,7 +308,7 @@ fn map_notification_method_to_observation_kind(
|
||||
}
|
||||
|
||||
/// Wraps one raw notification into a normalized JSON payload.
|
||||
fn build_notification_payload(notification: &crate::KbJsonRpcWsNotification) -> serde_json::Value {
|
||||
fn build_notification_payload(notification: &crate::JsonRpcWsNotification) -> serde_json::Value {
|
||||
return serde_json::json!({
|
||||
"jsonrpc": notification.jsonrpc,
|
||||
"method": notification.method,
|
||||
@@ -666,45 +666,45 @@ fn build_signal_kind_for_notification(
|
||||
fn build_signal_severity_for_notification(
|
||||
method: &str,
|
||||
result: &serde_json::Value,
|
||||
) -> crate::KbAnalysisSignalSeverity {
|
||||
) -> crate::AnalysisSignalSeverity {
|
||||
match method {
|
||||
"programNotification" => return crate::KbAnalysisSignalSeverity::Medium,
|
||||
"accountNotification" => return crate::KbAnalysisSignalSeverity::Low,
|
||||
"programNotification" => return crate::AnalysisSignalSeverity::Medium,
|
||||
"accountNotification" => return crate::AnalysisSignalSeverity::Low,
|
||||
"logsNotification" => {
|
||||
let lines = extract_logs_lines(result);
|
||||
for line in &lines {
|
||||
if line.contains("Instruction: InitializeMint") {
|
||||
return crate::KbAnalysisSignalSeverity::Medium;
|
||||
return crate::AnalysisSignalSeverity::Medium;
|
||||
}
|
||||
}
|
||||
return crate::KbAnalysisSignalSeverity::Low;
|
||||
return crate::AnalysisSignalSeverity::Low;
|
||||
},
|
||||
"signatureNotification" => {
|
||||
let err_option = extract_signature_notification_err(result);
|
||||
match err_option {
|
||||
Some(err) => {
|
||||
if err.is_null() {
|
||||
return crate::KbAnalysisSignalSeverity::Low;
|
||||
return crate::AnalysisSignalSeverity::Low;
|
||||
} else {
|
||||
return crate::KbAnalysisSignalSeverity::Medium;
|
||||
return crate::AnalysisSignalSeverity::Medium;
|
||||
}
|
||||
},
|
||||
None => return crate::KbAnalysisSignalSeverity::Low,
|
||||
None => return crate::AnalysisSignalSeverity::Low,
|
||||
}
|
||||
},
|
||||
_ => return crate::KbAnalysisSignalSeverity::Low,
|
||||
_ => return crate::AnalysisSignalSeverity::Low,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
async fn create_database() -> crate::KbDatabase {
|
||||
async fn create_database() -> crate::Database {
|
||||
let tempdir = tempfile::tempdir().expect("tempdir must succeed");
|
||||
let database_path = tempdir.path().join("detect_ws.sqlite3");
|
||||
let config = crate::KbDatabaseConfig {
|
||||
let config = crate::DatabaseConfig {
|
||||
enabled: true,
|
||||
backend: crate::KbDatabaseBackend::Sqlite,
|
||||
sqlite: crate::KbSqliteDatabaseConfig {
|
||||
backend: crate::DatabaseBackend::Sqlite,
|
||||
sqlite: crate::SqliteDatabaseConfig {
|
||||
path: database_path.to_string_lossy().to_string(),
|
||||
create_if_missing: true,
|
||||
busy_timeout_ms: 5000,
|
||||
@@ -713,16 +713,16 @@ mod tests {
|
||||
use_wal: true,
|
||||
},
|
||||
};
|
||||
return crate::KbDatabase::connect_and_initialize(&config)
|
||||
return crate::Database::connect_and_initialize(&config)
|
||||
.await
|
||||
.expect("database init must succeed");
|
||||
}
|
||||
|
||||
fn build_slot_notification() -> crate::KbJsonRpcWsNotification {
|
||||
return crate::KbJsonRpcWsNotification {
|
||||
fn build_slot_notification() -> crate::JsonRpcWsNotification {
|
||||
return crate::JsonRpcWsNotification {
|
||||
jsonrpc: "2.0".to_string(),
|
||||
method: "slotNotification".to_string(),
|
||||
params: crate::KbJsonRpcWsNotificationParams {
|
||||
params: crate::JsonRpcWsNotificationParams {
|
||||
result: serde_json::json!({
|
||||
"slot": 414726860_u64,
|
||||
"parent": 414726859_u64,
|
||||
@@ -733,11 +733,11 @@ mod tests {
|
||||
};
|
||||
}
|
||||
|
||||
fn build_program_mint_notification() -> crate::KbJsonRpcWsNotification {
|
||||
return crate::KbJsonRpcWsNotification {
|
||||
fn build_program_mint_notification() -> crate::JsonRpcWsNotification {
|
||||
return crate::JsonRpcWsNotification {
|
||||
jsonrpc: "2.0".to_string(),
|
||||
method: "programNotification".to_string(),
|
||||
params: crate::KbJsonRpcWsNotificationParams {
|
||||
params: crate::JsonRpcWsNotificationParams {
|
||||
result: serde_json::json!({
|
||||
"context": {
|
||||
"slot": 777777_u64
|
||||
@@ -763,11 +763,11 @@ mod tests {
|
||||
};
|
||||
}
|
||||
|
||||
fn build_program_pool_candidate_notification() -> crate::KbJsonRpcWsNotification {
|
||||
return crate::KbJsonRpcWsNotification {
|
||||
fn build_program_pool_candidate_notification() -> crate::JsonRpcWsNotification {
|
||||
return crate::JsonRpcWsNotification {
|
||||
jsonrpc: "2.0".to_string(),
|
||||
method: "programNotification".to_string(),
|
||||
params: crate::KbJsonRpcWsNotificationParams {
|
||||
params: crate::JsonRpcWsNotificationParams {
|
||||
result: serde_json::json!({
|
||||
"context": {
|
||||
"slot": 666666_u64
|
||||
@@ -788,11 +788,11 @@ mod tests {
|
||||
};
|
||||
}
|
||||
|
||||
fn build_logs_notification() -> crate::KbJsonRpcWsNotification {
|
||||
return crate::KbJsonRpcWsNotification {
|
||||
fn build_logs_notification() -> crate::JsonRpcWsNotification {
|
||||
return crate::JsonRpcWsNotification {
|
||||
jsonrpc: "2.0".to_string(),
|
||||
method: "logsNotification".to_string(),
|
||||
params: crate::KbJsonRpcWsNotificationParams {
|
||||
params: crate::JsonRpcWsNotificationParams {
|
||||
result: serde_json::json!({
|
||||
"context": {
|
||||
"slot": 888888_u64
|
||||
@@ -810,11 +810,11 @@ mod tests {
|
||||
};
|
||||
}
|
||||
|
||||
fn build_signature_notification() -> crate::KbJsonRpcWsNotification {
|
||||
return crate::KbJsonRpcWsNotification {
|
||||
fn build_signature_notification() -> crate::JsonRpcWsNotification {
|
||||
return crate::JsonRpcWsNotification {
|
||||
jsonrpc: "2.0".to_string(),
|
||||
method: "signatureNotification".to_string(),
|
||||
params: crate::KbJsonRpcWsNotificationParams {
|
||||
params: crate::JsonRpcWsNotificationParams {
|
||||
result: serde_json::json!({
|
||||
"context": {
|
||||
"slot": 999999_u64
|
||||
@@ -831,8 +831,8 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn slot_notification_records_observation() {
|
||||
let database = create_database().await;
|
||||
let persistence = crate::KbDetectionPersistenceService::new(std::sync::Arc::new(database));
|
||||
let detector = crate::KbSolanaWsDetectionService::new(persistence);
|
||||
let persistence = crate::DetectionPersistenceService::new(std::sync::Arc::new(database));
|
||||
let detector = crate::SolanaWsDetectionService::new(persistence);
|
||||
let outcome_result = detector
|
||||
.process_notification(
|
||||
Some("mainnet_public_ws_slots".to_string()),
|
||||
@@ -844,14 +844,16 @@ mod tests {
|
||||
Err(error) => panic!("process_notification failed: {error}"),
|
||||
};
|
||||
match outcome {
|
||||
crate::KbSolanaWsDetectionOutcome::ObservationRecorded { observation_id } => {
|
||||
crate::SolanaWsDetectionOutcome::ObservationRecorded { observation_id } => {
|
||||
assert!(observation_id > 0);
|
||||
},
|
||||
_ => panic!("unexpected detection outcome"),
|
||||
}
|
||||
let observations_result =
|
||||
crate::list_recent_onchain_observations(detector.persistence().database().as_ref(), 10)
|
||||
.await;
|
||||
let observations_result = crate::query_onchain_observations_list_recent(
|
||||
detector.persistence().database().as_ref(),
|
||||
10,
|
||||
)
|
||||
.await;
|
||||
let observations = match observations_result {
|
||||
Ok(observations) => observations,
|
||||
Err(error) => panic!("list_recent_onchain_observations failed: {error}"),
|
||||
@@ -864,8 +866,8 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn program_mint_notification_registers_token_candidate() {
|
||||
let database = create_database().await;
|
||||
let persistence = crate::KbDetectionPersistenceService::new(std::sync::Arc::new(database));
|
||||
let detector = crate::KbSolanaWsDetectionService::new(persistence);
|
||||
let persistence = crate::DetectionPersistenceService::new(std::sync::Arc::new(database));
|
||||
let detector = crate::SolanaWsDetectionService::new(persistence);
|
||||
let outcome_result = detector
|
||||
.process_notification(
|
||||
Some("helius_primary_ws_programs".to_string()),
|
||||
@@ -877,14 +879,14 @@ mod tests {
|
||||
Err(error) => panic!("process_notification failed: {error}"),
|
||||
};
|
||||
match outcome {
|
||||
crate::KbSolanaWsDetectionOutcome::TokenCandidateRegistered { result } => {
|
||||
crate::SolanaWsDetectionOutcome::TokenCandidateRegistered { result } => {
|
||||
assert!(result.token_id > 0);
|
||||
assert!(result.observation_id > 0);
|
||||
assert!(result.signal_id > 0);
|
||||
},
|
||||
_ => panic!("unexpected detection outcome"),
|
||||
}
|
||||
let token_result = crate::get_token_by_mint(
|
||||
let token_result = crate::query_tokens_get_by_mint(
|
||||
detector.persistence().database().as_ref(),
|
||||
"Mint111111111111111111111111111111111111111",
|
||||
)
|
||||
@@ -894,15 +896,17 @@ mod tests {
|
||||
Err(error) => panic!("get_token_by_mint failed: {error}"),
|
||||
};
|
||||
assert!(token_option.is_some());
|
||||
let observations_result =
|
||||
crate::list_recent_onchain_observations(detector.persistence().database().as_ref(), 10)
|
||||
.await;
|
||||
let observations_result = crate::query_onchain_observations_list_recent(
|
||||
detector.persistence().database().as_ref(),
|
||||
10,
|
||||
)
|
||||
.await;
|
||||
let observations = match observations_result {
|
||||
Ok(observations) => observations,
|
||||
Err(error) => panic!("list_recent_onchain_observations failed: {error}"),
|
||||
};
|
||||
let signals_result =
|
||||
crate::list_recent_analysis_signals(detector.persistence().database().as_ref(), 10)
|
||||
crate::query_analysis_signals_list(detector.persistence().database().as_ref(), 10)
|
||||
.await;
|
||||
let signals = match signals_result {
|
||||
Ok(signals) => signals,
|
||||
@@ -917,8 +921,8 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn logs_notification_records_observation_and_signal() {
|
||||
let database = create_database().await;
|
||||
let persistence = crate::KbDetectionPersistenceService::new(std::sync::Arc::new(database));
|
||||
let detector = crate::KbSolanaWsDetectionService::new(persistence);
|
||||
let persistence = crate::DetectionPersistenceService::new(std::sync::Arc::new(database));
|
||||
let detector = crate::SolanaWsDetectionService::new(persistence);
|
||||
let outcome_result = detector
|
||||
.process_notification(
|
||||
Some("helius_primary_ws_programs".to_string()),
|
||||
@@ -930,20 +934,22 @@ mod tests {
|
||||
Err(error) => panic!("process_notification failed: {error}"),
|
||||
};
|
||||
match outcome {
|
||||
crate::KbSolanaWsDetectionOutcome::ObservationRecorded { observation_id } => {
|
||||
crate::SolanaWsDetectionOutcome::ObservationRecorded { observation_id } => {
|
||||
assert!(observation_id > 0);
|
||||
},
|
||||
_ => panic!("unexpected detection outcome"),
|
||||
}
|
||||
let observations_result =
|
||||
crate::list_recent_onchain_observations(detector.persistence().database().as_ref(), 10)
|
||||
.await;
|
||||
let observations_result = crate::query_onchain_observations_list_recent(
|
||||
detector.persistence().database().as_ref(),
|
||||
10,
|
||||
)
|
||||
.await;
|
||||
let observations = match observations_result {
|
||||
Ok(observations) => observations,
|
||||
Err(error) => panic!("list_recent_onchain_observations failed: {error}"),
|
||||
};
|
||||
let signals_result =
|
||||
crate::list_recent_analysis_signals(detector.persistence().database().as_ref(), 10)
|
||||
crate::query_analysis_signals_list(detector.persistence().database().as_ref(), 10)
|
||||
.await;
|
||||
let signals = match signals_result {
|
||||
Ok(signals) => signals,
|
||||
@@ -957,8 +963,8 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn signature_notification_records_observation_and_signal() {
|
||||
let database = create_database().await;
|
||||
let persistence = crate::KbDetectionPersistenceService::new(std::sync::Arc::new(database));
|
||||
let detector = crate::KbSolanaWsDetectionService::new(persistence);
|
||||
let persistence = crate::DetectionPersistenceService::new(std::sync::Arc::new(database));
|
||||
let detector = crate::SolanaWsDetectionService::new(persistence);
|
||||
let outcome_result = detector
|
||||
.process_notification(
|
||||
Some("mainnet_public_ws_slots".to_string()),
|
||||
@@ -970,20 +976,22 @@ mod tests {
|
||||
Err(error) => panic!("process_notification failed: {error}"),
|
||||
};
|
||||
match outcome {
|
||||
crate::KbSolanaWsDetectionOutcome::ObservationRecorded { observation_id } => {
|
||||
crate::SolanaWsDetectionOutcome::ObservationRecorded { observation_id } => {
|
||||
assert!(observation_id > 0);
|
||||
},
|
||||
_ => panic!("unexpected detection outcome"),
|
||||
}
|
||||
let observations_result =
|
||||
crate::list_recent_onchain_observations(detector.persistence().database().as_ref(), 10)
|
||||
.await;
|
||||
let observations_result = crate::query_onchain_observations_list_recent(
|
||||
detector.persistence().database().as_ref(),
|
||||
10,
|
||||
)
|
||||
.await;
|
||||
let observations = match observations_result {
|
||||
Ok(observations) => observations,
|
||||
Err(error) => panic!("list_recent_onchain_observations failed: {error}"),
|
||||
};
|
||||
let signals_result =
|
||||
crate::list_recent_analysis_signals(detector.persistence().database().as_ref(), 10)
|
||||
crate::query_analysis_signals_list(detector.persistence().database().as_ref(), 10)
|
||||
.await;
|
||||
let signals = match signals_result {
|
||||
Ok(signals) => signals,
|
||||
@@ -997,10 +1005,10 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn program_pool_candidate_notification_registers_pool_candidate() {
|
||||
let database = create_database().await;
|
||||
let persistence = crate::KbDetectionPersistenceService::new(std::sync::Arc::new(database));
|
||||
let dex_id = crate::upsert_dex(
|
||||
let persistence = crate::DetectionPersistenceService::new(std::sync::Arc::new(database));
|
||||
let dex_id = crate::query_dexs_upsert(
|
||||
persistence.database().as_ref(),
|
||||
&crate::KbDexDto::new(
|
||||
&crate::DexDto::new(
|
||||
"raydium".to_string(),
|
||||
"Raydium".to_string(),
|
||||
Some("DexProgram111111111111111111111111111111111".to_string()),
|
||||
@@ -1011,7 +1019,7 @@ mod tests {
|
||||
.await
|
||||
.expect("upsert dex must succeed");
|
||||
assert!(dex_id > 0);
|
||||
let detector = crate::KbSolanaWsDetectionService::new(persistence);
|
||||
let detector = crate::SolanaWsDetectionService::new(persistence);
|
||||
let outcome_result = detector
|
||||
.process_notification(
|
||||
Some("helius_primary_ws_programs".to_string()),
|
||||
@@ -1023,7 +1031,7 @@ mod tests {
|
||||
Err(error) => panic!("process_notification failed: {error}"),
|
||||
};
|
||||
let pool_id = match outcome {
|
||||
crate::KbSolanaWsDetectionOutcome::PoolCandidateRegistered { result } => {
|
||||
crate::SolanaWsDetectionOutcome::PoolCandidateRegistered { result } => {
|
||||
assert!(result.dex_id > 0);
|
||||
assert!(result.pool_id > 0);
|
||||
assert!(result.pool_listing_id > 0);
|
||||
@@ -1031,7 +1039,7 @@ mod tests {
|
||||
},
|
||||
_ => panic!("unexpected detection outcome"),
|
||||
};
|
||||
let pool_result = crate::get_pool_by_address(
|
||||
let pool_result = crate::query_pools_get_by_address(
|
||||
detector.persistence().database().as_ref(),
|
||||
"Pool111111111111111111111111111111111111111",
|
||||
)
|
||||
@@ -1041,9 +1049,11 @@ mod tests {
|
||||
Err(error) => panic!("get_pool_by_address failed: {error}"),
|
||||
};
|
||||
assert!(pool_option.is_some());
|
||||
let listing_result =
|
||||
crate::get_pool_listing_by_pool_id(detector.persistence().database().as_ref(), pool_id)
|
||||
.await;
|
||||
let listing_result = crate::query_pool_listings_get_by_pool_id(
|
||||
detector.persistence().database().as_ref(),
|
||||
pool_id,
|
||||
)
|
||||
.await;
|
||||
let listing_option = match listing_result {
|
||||
Ok(listing_option) => listing_option,
|
||||
Err(error) => panic!("get_pool_listing_by_pool_id failed: {error}"),
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
|
||||
/// One normalized observation ready to be persisted.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct KbDetectionObservationInput {
|
||||
pub struct DetectionObservationInput {
|
||||
/// Observation kind.
|
||||
pub observation_kind: std::string::String,
|
||||
/// Observation source family.
|
||||
pub source_kind: crate::KbObservationSourceKind,
|
||||
pub source_kind: crate::ObservationSourceKind,
|
||||
/// Optional logical source endpoint name.
|
||||
pub endpoint_name: std::option::Option<std::string::String>,
|
||||
/// Logical object key, for example a mint, signature or pool address.
|
||||
@@ -19,11 +19,11 @@ pub struct KbDetectionObservationInput {
|
||||
pub payload: serde_json::Value,
|
||||
}
|
||||
|
||||
impl KbDetectionObservationInput {
|
||||
impl DetectionObservationInput {
|
||||
/// Creates a new detection observation input.
|
||||
pub fn new(
|
||||
observation_kind: std::string::String,
|
||||
source_kind: crate::KbObservationSourceKind,
|
||||
source_kind: crate::ObservationSourceKind,
|
||||
endpoint_name: std::option::Option<std::string::String>,
|
||||
object_key: std::string::String,
|
||||
slot: std::option::Option<u64>,
|
||||
@@ -42,11 +42,11 @@ impl KbDetectionObservationInput {
|
||||
|
||||
/// One normalized signal ready to be persisted.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct KbDetectionSignalInput {
|
||||
pub struct DetectionSignalInput {
|
||||
/// Signal kind.
|
||||
pub signal_kind: std::string::String,
|
||||
/// Signal severity.
|
||||
pub severity: crate::KbAnalysisSignalSeverity,
|
||||
pub severity: crate::AnalysisSignalSeverity,
|
||||
/// Logical object key, for example a mint, signature or pool address.
|
||||
pub object_key: std::string::String,
|
||||
/// Optional related observation id.
|
||||
@@ -57,11 +57,11 @@ pub struct KbDetectionSignalInput {
|
||||
pub payload: serde_json::Value,
|
||||
}
|
||||
|
||||
impl KbDetectionSignalInput {
|
||||
impl DetectionSignalInput {
|
||||
/// Creates a new detection signal input.
|
||||
pub fn new(
|
||||
signal_kind: std::string::String,
|
||||
severity: crate::KbAnalysisSignalSeverity,
|
||||
severity: crate::AnalysisSignalSeverity,
|
||||
object_key: std::string::String,
|
||||
related_observation_id: std::option::Option<i64>,
|
||||
score: std::option::Option<f64>,
|
||||
@@ -80,7 +80,7 @@ impl KbDetectionSignalInput {
|
||||
|
||||
/// One token candidate detected from a technical source.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct KbDetectionTokenCandidateInput {
|
||||
pub struct DetectionTokenCandidateInput {
|
||||
/// Token mint address.
|
||||
pub mint: std::string::String,
|
||||
/// Optional token symbol.
|
||||
@@ -94,7 +94,7 @@ pub struct KbDetectionTokenCandidateInput {
|
||||
/// Whether the token is typically used as quote token.
|
||||
pub is_quote_token: bool,
|
||||
/// Observation source family.
|
||||
pub source_kind: crate::KbObservationSourceKind,
|
||||
pub source_kind: crate::ObservationSourceKind,
|
||||
/// Optional source endpoint logical name.
|
||||
pub endpoint_name: std::option::Option<std::string::String>,
|
||||
/// Optional slot number.
|
||||
@@ -106,14 +106,14 @@ pub struct KbDetectionTokenCandidateInput {
|
||||
/// Signal kind.
|
||||
pub signal_kind: std::string::String,
|
||||
/// Signal severity.
|
||||
pub signal_severity: crate::KbAnalysisSignalSeverity,
|
||||
pub signal_severity: crate::AnalysisSignalSeverity,
|
||||
/// Optional signal score.
|
||||
pub signal_score: std::option::Option<f64>,
|
||||
/// Optional dedicated signal payload.
|
||||
pub signal_payload: std::option::Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
impl KbDetectionTokenCandidateInput {
|
||||
impl DetectionTokenCandidateInput {
|
||||
/// Creates a new token candidate input.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
@@ -123,13 +123,13 @@ impl KbDetectionTokenCandidateInput {
|
||||
decimals: std::option::Option<u8>,
|
||||
token_program: std::string::String,
|
||||
is_quote_token: bool,
|
||||
source_kind: crate::KbObservationSourceKind,
|
||||
source_kind: crate::ObservationSourceKind,
|
||||
endpoint_name: std::option::Option<std::string::String>,
|
||||
slot: std::option::Option<u64>,
|
||||
observation_kind: std::string::String,
|
||||
observation_payload: serde_json::Value,
|
||||
signal_kind: std::string::String,
|
||||
signal_severity: crate::KbAnalysisSignalSeverity,
|
||||
signal_severity: crate::AnalysisSignalSeverity,
|
||||
signal_score: std::option::Option<f64>,
|
||||
signal_payload: std::option::Option<serde_json::Value>,
|
||||
) -> Self {
|
||||
@@ -155,7 +155,7 @@ impl KbDetectionTokenCandidateInput {
|
||||
|
||||
/// Result of one token candidate persistence operation.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct KbDetectionTokenCandidateResult {
|
||||
pub struct DetectionTokenCandidateResult {
|
||||
/// Persisted token id.
|
||||
pub token_id: i64,
|
||||
/// Persisted observation id.
|
||||
@@ -166,13 +166,13 @@ pub struct KbDetectionTokenCandidateResult {
|
||||
|
||||
/// One pool candidate detected from a technical source.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct KbDetectionPoolCandidateInput {
|
||||
pub struct DetectionPoolCandidateInput {
|
||||
/// Pool address.
|
||||
pub pool_address: std::string::String,
|
||||
/// DEX program id or router program id that matched.
|
||||
pub dex_program_id: std::string::String,
|
||||
/// Observation source family.
|
||||
pub source_kind: crate::KbObservationSourceKind,
|
||||
pub source_kind: crate::ObservationSourceKind,
|
||||
/// Optional source endpoint logical name.
|
||||
pub endpoint_name: std::option::Option<std::string::String>,
|
||||
/// Optional slot number.
|
||||
@@ -184,26 +184,26 @@ pub struct KbDetectionPoolCandidateInput {
|
||||
/// Signal kind.
|
||||
pub signal_kind: std::string::String,
|
||||
/// Signal severity.
|
||||
pub signal_severity: crate::KbAnalysisSignalSeverity,
|
||||
pub signal_severity: crate::AnalysisSignalSeverity,
|
||||
/// Optional signal score.
|
||||
pub signal_score: std::option::Option<f64>,
|
||||
/// Optional dedicated signal payload.
|
||||
pub signal_payload: std::option::Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
impl KbDetectionPoolCandidateInput {
|
||||
impl DetectionPoolCandidateInput {
|
||||
/// Creates a new pool candidate input.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
pool_address: std::string::String,
|
||||
dex_program_id: std::string::String,
|
||||
source_kind: crate::KbObservationSourceKind,
|
||||
source_kind: crate::ObservationSourceKind,
|
||||
endpoint_name: std::option::Option<std::string::String>,
|
||||
slot: std::option::Option<u64>,
|
||||
observation_kind: std::string::String,
|
||||
observation_payload: serde_json::Value,
|
||||
signal_kind: std::string::String,
|
||||
signal_severity: crate::KbAnalysisSignalSeverity,
|
||||
signal_severity: crate::AnalysisSignalSeverity,
|
||||
signal_score: std::option::Option<f64>,
|
||||
signal_payload: std::option::Option<serde_json::Value>,
|
||||
) -> Self {
|
||||
@@ -225,7 +225,7 @@ impl KbDetectionPoolCandidateInput {
|
||||
|
||||
/// Result of one pool candidate persistence operation.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct KbDetectionPoolCandidateResult {
|
||||
pub struct DetectionPoolCandidateResult {
|
||||
/// Persisted dex id.
|
||||
pub dex_id: i64,
|
||||
/// Persisted pool id.
|
||||
|
||||
@@ -7,18 +7,18 @@
|
||||
|
||||
/// One forwarded WebSocket notification ready for detection.
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct KbWsDetectionNotificationEnvelope {
|
||||
pub struct WsDetectionNotificationEnvelope {
|
||||
/// Optional logical endpoint name.
|
||||
pub endpoint_name: std::option::Option<std::string::String>,
|
||||
/// The parsed JSON-RPC notification.
|
||||
pub notification: crate::KbJsonRpcWsNotification,
|
||||
pub notification: crate::JsonRpcWsNotification,
|
||||
}
|
||||
|
||||
impl KbWsDetectionNotificationEnvelope {
|
||||
impl WsDetectionNotificationEnvelope {
|
||||
/// Creates a new notification envelope.
|
||||
pub fn new(
|
||||
endpoint_name: std::option::Option<std::string::String>,
|
||||
notification: crate::KbJsonRpcWsNotification,
|
||||
notification: crate::JsonRpcWsNotification,
|
||||
) -> Self {
|
||||
return Self { endpoint_name, notification };
|
||||
}
|
||||
@@ -26,7 +26,7 @@ impl KbWsDetectionNotificationEnvelope {
|
||||
|
||||
/// Runtime statistics for one relay worker.
|
||||
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
|
||||
pub struct KbWsDetectionRelayStats {
|
||||
pub struct WsDetectionRelayStats {
|
||||
/// Number of received envelopes.
|
||||
pub received_count: u64,
|
||||
/// Number of ignored notifications.
|
||||
@@ -43,14 +43,14 @@ pub struct KbWsDetectionRelayStats {
|
||||
|
||||
/// Asynchronous relay between `WsClient` notifications and the detection service.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct KbWsDetectionRelay {
|
||||
pub struct WsDetectionRelay {
|
||||
/// Solana WS detection service.
|
||||
detector: crate::KbSolanaWsDetectionService,
|
||||
detector: crate::SolanaWsDetectionService,
|
||||
}
|
||||
|
||||
impl KbWsDetectionRelay {
|
||||
impl WsDetectionRelay {
|
||||
/// Creates a new relay.
|
||||
pub fn new(detector: crate::KbSolanaWsDetectionService) -> Self {
|
||||
pub fn new(detector: crate::SolanaWsDetectionService) -> Self {
|
||||
return Self { detector };
|
||||
}
|
||||
|
||||
@@ -58,8 +58,8 @@ impl KbWsDetectionRelay {
|
||||
pub fn channel(
|
||||
capacity: usize,
|
||||
) -> (
|
||||
tokio::sync::mpsc::Sender<crate::KbWsDetectionNotificationEnvelope>,
|
||||
tokio::sync::mpsc::Receiver<crate::KbWsDetectionNotificationEnvelope>,
|
||||
tokio::sync::mpsc::Sender<crate::WsDetectionNotificationEnvelope>,
|
||||
tokio::sync::mpsc::Receiver<crate::WsDetectionNotificationEnvelope>,
|
||||
) {
|
||||
return tokio::sync::mpsc::channel(capacity);
|
||||
}
|
||||
@@ -67,8 +67,8 @@ impl KbWsDetectionRelay {
|
||||
/// Processes one forwarded notification.
|
||||
pub async fn process_envelope(
|
||||
&self,
|
||||
envelope: &crate::KbWsDetectionNotificationEnvelope,
|
||||
) -> Result<crate::KbSolanaWsDetectionOutcome, crate::KbError> {
|
||||
envelope: &crate::WsDetectionNotificationEnvelope,
|
||||
) -> Result<crate::SolanaWsDetectionOutcome, crate::Error> {
|
||||
return self
|
||||
.detector
|
||||
.process_notification(envelope.endpoint_name.clone(), &envelope.notification)
|
||||
@@ -78,10 +78,10 @@ impl KbWsDetectionRelay {
|
||||
/// Spawns one background relay worker.
|
||||
pub fn spawn(
|
||||
self,
|
||||
mut receiver: tokio::sync::mpsc::Receiver<crate::KbWsDetectionNotificationEnvelope>,
|
||||
) -> tokio::task::JoinHandle<crate::KbWsDetectionRelayStats> {
|
||||
mut receiver: tokio::sync::mpsc::Receiver<crate::WsDetectionNotificationEnvelope>,
|
||||
) -> tokio::task::JoinHandle<crate::WsDetectionRelayStats> {
|
||||
return tokio::spawn(async move {
|
||||
let mut stats = crate::KbWsDetectionRelayStats::default();
|
||||
let mut stats = crate::WsDetectionRelayStats::default();
|
||||
loop {
|
||||
let recv_result = receiver.recv().await;
|
||||
let envelope = match recv_result {
|
||||
@@ -95,7 +95,7 @@ impl KbWsDetectionRelay {
|
||||
Err(error) => {
|
||||
stats.error_count += 1;
|
||||
tracing::error!(
|
||||
target: "kb_lib::detect::ws_relay",
|
||||
target: "detect::ws_relay",
|
||||
"ws detection relay processing failed endpoint_name={:?}: {}",
|
||||
envelope.endpoint_name,
|
||||
error
|
||||
@@ -104,16 +104,16 @@ impl KbWsDetectionRelay {
|
||||
},
|
||||
};
|
||||
match outcome {
|
||||
crate::KbSolanaWsDetectionOutcome::Ignored => {
|
||||
crate::SolanaWsDetectionOutcome::Ignored => {
|
||||
stats.ignored_count += 1;
|
||||
},
|
||||
crate::KbSolanaWsDetectionOutcome::ObservationRecorded { .. } => {
|
||||
crate::SolanaWsDetectionOutcome::ObservationRecorded { .. } => {
|
||||
stats.observation_count += 1;
|
||||
},
|
||||
crate::KbSolanaWsDetectionOutcome::TokenCandidateRegistered { .. } => {
|
||||
crate::SolanaWsDetectionOutcome::TokenCandidateRegistered { .. } => {
|
||||
stats.token_candidate_count += 1;
|
||||
},
|
||||
crate::KbSolanaWsDetectionOutcome::PoolCandidateRegistered { .. } => {
|
||||
crate::SolanaWsDetectionOutcome::PoolCandidateRegistered { .. } => {
|
||||
stats.pool_candidate_count += 1;
|
||||
},
|
||||
}
|
||||
@@ -125,13 +125,13 @@ impl KbWsDetectionRelay {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
async fn create_database() -> crate::KbDatabase {
|
||||
async fn create_database() -> crate::Database {
|
||||
let tempdir = tempfile::tempdir().expect("tempdir must succeed");
|
||||
let database_path = tempdir.path().join("ws_relay.sqlite3");
|
||||
let config = crate::KbDatabaseConfig {
|
||||
let config = crate::DatabaseConfig {
|
||||
enabled: true,
|
||||
backend: crate::KbDatabaseBackend::Sqlite,
|
||||
sqlite: crate::KbSqliteDatabaseConfig {
|
||||
backend: crate::DatabaseBackend::Sqlite,
|
||||
sqlite: crate::SqliteDatabaseConfig {
|
||||
path: database_path.to_string_lossy().to_string(),
|
||||
create_if_missing: true,
|
||||
busy_timeout_ms: 5000,
|
||||
@@ -140,16 +140,16 @@ mod tests {
|
||||
use_wal: true,
|
||||
},
|
||||
};
|
||||
return crate::KbDatabase::connect_and_initialize(&config)
|
||||
return crate::Database::connect_and_initialize(&config)
|
||||
.await
|
||||
.expect("database init must succeed");
|
||||
}
|
||||
|
||||
fn build_slot_notification() -> crate::KbJsonRpcWsNotification {
|
||||
return crate::KbJsonRpcWsNotification {
|
||||
fn build_slot_notification() -> crate::JsonRpcWsNotification {
|
||||
return crate::JsonRpcWsNotification {
|
||||
jsonrpc: "2.0".to_string(),
|
||||
method: "slotNotification".to_string(),
|
||||
params: crate::KbJsonRpcWsNotificationParams {
|
||||
params: crate::JsonRpcWsNotificationParams {
|
||||
result: serde_json::json!({
|
||||
"slot": 414726860_u64,
|
||||
"parent": 414726859_u64,
|
||||
@@ -163,10 +163,10 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn relay_process_envelope_records_observation() {
|
||||
let database = create_database().await;
|
||||
let persistence = crate::KbDetectionPersistenceService::new(std::sync::Arc::new(database));
|
||||
let detector = crate::KbSolanaWsDetectionService::new(persistence);
|
||||
let relay = crate::KbWsDetectionRelay::new(detector);
|
||||
let envelope = crate::KbWsDetectionNotificationEnvelope::new(
|
||||
let persistence = crate::DetectionPersistenceService::new(std::sync::Arc::new(database));
|
||||
let detector = crate::SolanaWsDetectionService::new(persistence);
|
||||
let relay = crate::WsDetectionRelay::new(detector);
|
||||
let envelope = crate::WsDetectionNotificationEnvelope::new(
|
||||
Some("mainnet_public_ws_slots".to_string()),
|
||||
build_slot_notification(),
|
||||
);
|
||||
@@ -176,7 +176,7 @@ mod tests {
|
||||
Err(error) => panic!("process_envelope failed: {error}"),
|
||||
};
|
||||
match outcome {
|
||||
crate::KbSolanaWsDetectionOutcome::ObservationRecorded { observation_id } => {
|
||||
crate::SolanaWsDetectionOutcome::ObservationRecorded { observation_id } => {
|
||||
assert!(observation_id > 0);
|
||||
},
|
||||
_ => panic!("unexpected relay outcome"),
|
||||
@@ -186,13 +186,13 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn relay_worker_processes_channel() {
|
||||
let database = create_database().await;
|
||||
let persistence = crate::KbDetectionPersistenceService::new(std::sync::Arc::new(database));
|
||||
let detector = crate::KbSolanaWsDetectionService::new(persistence);
|
||||
let relay = crate::KbWsDetectionRelay::new(detector);
|
||||
let (sender, receiver) = crate::KbWsDetectionRelay::channel(8);
|
||||
let persistence = crate::DetectionPersistenceService::new(std::sync::Arc::new(database));
|
||||
let detector = crate::SolanaWsDetectionService::new(persistence);
|
||||
let relay = crate::WsDetectionRelay::new(detector);
|
||||
let (sender, receiver) = crate::WsDetectionRelay::channel(8);
|
||||
let handle = relay.spawn(receiver);
|
||||
let send_result = sender
|
||||
.send(crate::KbWsDetectionNotificationEnvelope::new(
|
||||
.send(crate::WsDetectionNotificationEnvelope::new(
|
||||
Some("mainnet_public_ws_slots".to_string()),
|
||||
build_slot_notification(),
|
||||
))
|
||||
|
||||
Reference in New Issue
Block a user