This commit is contained in:
2026-05-05 05:03:11 +02:00
parent 3e994995d7
commit f2c227e08f
132 changed files with 5767 additions and 4461 deletions

View File

@@ -12,11 +12,11 @@ pub struct KbDetectionPersistenceService {
impl KbDetectionPersistenceService {
/// Creates a new detection persistence service.
pub fn new(database: std::sync::Arc<crate::KbDatabase>) -> Self {
Self { database }
return Self { database };
}
/// Returns the shared database handle.
pub fn database(&self) -> &std::sync::Arc<crate::KbDatabase> {
&self.database
return &self.database;
}
/// Persists one on-chain observation.
@@ -32,7 +32,7 @@ impl KbDetectionPersistenceService {
input.slot,
input.payload.clone(),
);
crate::insert_onchain_observation(self.database.as_ref(), &dto).await
return crate::insert_onchain_observation(self.database.as_ref(), &dto).await;
}
/// Persists one analysis signal.
@@ -48,7 +48,7 @@ impl KbDetectionPersistenceService {
input.score,
input.payload.clone(),
);
crate::insert_analysis_signal(self.database.as_ref(), &dto).await
return crate::insert_analysis_signal(self.database.as_ref(), &dto).await;
}
/// Registers one token candidate from a technical source.
@@ -104,11 +104,7 @@ impl KbDetectionPersistenceService {
Ok(signal_id) => signal_id,
Err(error) => return Err(error),
};
Ok(crate::KbDetectionTokenCandidateResult {
token_id,
observation_id,
signal_id,
})
return Ok(crate::KbDetectionTokenCandidateResult { token_id, observation_id, signal_id });
}
/// Registers one pool candidate from a technical source.
@@ -154,7 +150,7 @@ impl KbDetectionPersistenceService {
"cannot register pool candidate: no known dex matches program id '{}'",
input.dex_program_id
)));
}
},
};
let dex_id = match matched_dex.id {
Some(dex_id) => dex_id,
@@ -162,7 +158,7 @@ impl KbDetectionPersistenceService {
return Err(crate::KbError::Db(
"cannot register pool candidate: matched dex has no id".to_string(),
));
}
},
};
let pool_dto = crate::KbPoolDto::new(
dex_id,
@@ -221,13 +217,13 @@ impl KbDetectionPersistenceService {
Ok(signal_id) => signal_id,
Err(error) => return Err(error),
};
Ok(crate::KbDetectionPoolCandidateResult {
return Ok(crate::KbDetectionPoolCandidateResult {
dex_id,
pool_id,
pool_listing_id,
observation_id,
signal_id,
})
});
}
}
@@ -248,9 +244,9 @@ mod tests {
use_wal: true,
},
};
crate::KbDatabase::connect_and_initialize(&config)
return crate::KbDatabase::connect_and_initialize(&config)
.await
.expect("database init must succeed")
.expect("database init must succeed");
}
#[tokio::test]
@@ -293,14 +289,8 @@ mod tests {
.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, "So11111111111111111111111111111111111111112");
assert_eq!(signals[0].object_key, "So11111111111111111111111111111111111111112");
}
#[tokio::test]
@@ -340,10 +330,7 @@ mod tests {
.await
.expect("get token must succeed");
assert!(token.is_some());
assert_eq!(
token.expect("token must exist").symbol.as_deref(),
Some("TEST")
);
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");
@@ -352,18 +339,9 @@ mod tests {
.expect("list signals must succeed");
assert_eq!(observations.len(), 1);
assert_eq!(signals.len(), 1);
assert_eq!(
observations[0].object_key,
"Mint111111111111111111111111111111111111111"
);
assert_eq!(
signals[0].object_key,
"Mint111111111111111111111111111111111111111"
);
assert_eq!(
signals[0].related_observation_id,
Some(result.observation_id)
);
assert_eq!(observations[0].object_key, "Mint111111111111111111111111111111111111111");
assert_eq!(signals[0].object_key, "Mint111111111111111111111111111111111111111");
assert_eq!(signals[0].related_observation_id, Some(result.observation_id));
}
#[tokio::test]

View File

@@ -37,12 +37,12 @@ pub struct KbSolanaWsDetectionService {
impl KbSolanaWsDetectionService {
/// Creates a new Solana WebSocket detection service.
pub fn new(persistence: crate::KbDetectionPersistenceService) -> Self {
Self { persistence }
return Self { persistence };
}
/// Returns the shared persistence façade.
pub fn persistence(&self) -> &crate::KbDetectionPersistenceService {
&self.persistence
return &self.persistence;
}
/// Processes one Solana WebSocket JSON-RPC notification.
@@ -57,24 +57,22 @@ impl KbSolanaWsDetectionService {
Some(observation_kind) => observation_kind,
None => return Ok(crate::KbSolanaWsDetectionOutcome::Ignored),
};
let token_candidate_result = self
.try_register_token_candidate(endpoint_name.clone(), notification)
.await;
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 });
}
Ok(None) => {}
},
Ok(None) => {},
Err(error) => return Err(error),
}
let pool_candidate_result = self
.try_register_pool_candidate(endpoint_name.clone(), notification)
.await;
let pool_candidate_result =
self.try_register_pool_candidate(endpoint_name.clone(), notification).await;
match pool_candidate_result {
Ok(Some(result)) => {
return Ok(crate::KbSolanaWsDetectionOutcome::PoolCandidateRegistered { result });
}
Ok(None) => {}
},
Ok(None) => {},
Err(error) => return Err(error),
}
let payload = build_notification_payload(notification);
@@ -93,10 +91,7 @@ impl KbSolanaWsDetectionService {
slot,
payload.clone(),
);
let observation_id_result = self
.persistence
.record_observation(&observation_input)
.await;
let observation_id_result = self.persistence.record_observation(&observation_input).await;
let observation_id = match observation_id_result {
Ok(observation_id) => observation_id,
Err(error) => return Err(error),
@@ -127,7 +122,7 @@ impl KbSolanaWsDetectionService {
return Err(error);
}
}
Ok(crate::KbSolanaWsDetectionOutcome::ObservationRecorded { observation_id })
return Ok(crate::KbSolanaWsDetectionOutcome::ObservationRecorded { observation_id });
}
/// Tries to register a token candidate from one notification.
@@ -210,8 +205,8 @@ impl KbSolanaWsDetectionService {
);
let result = self.persistence.register_token_candidate(&input).await;
match result {
Ok(result) => Ok(Some(result)),
Err(error) => Err(error),
Ok(result) => return Ok(Some(result)),
Err(error) => return Err(error),
}
}
@@ -288,8 +283,8 @@ impl KbSolanaWsDetectionService {
);
let result = self.persistence.register_pool_candidate(&input).await;
match result {
Ok(result) => Ok(Some(result)),
Err(error) => Err(error),
Ok(result) => return Ok(Some(result)),
Err(error) => return Err(error),
}
}
}
@@ -299,27 +294,27 @@ fn map_notification_method_to_observation_kind(
method: &str,
) -> std::option::Option<std::string::String> {
match method {
"accountNotification" => Some("ws.account_notification".to_string()),
"blockNotification" => Some("ws.block_notification".to_string()),
"logsNotification" => Some("ws.logs_notification".to_string()),
"programNotification" => Some("ws.program_notification".to_string()),
"rootNotification" => Some("ws.root_notification".to_string()),
"signatureNotification" => Some("ws.signature_notification".to_string()),
"slotNotification" => Some("ws.slot_notification".to_string()),
"slotsUpdatesNotification" => Some("ws.slots_updates_notification".to_string()),
"voteNotification" => Some("ws.vote_notification".to_string()),
_ => None,
"accountNotification" => return Some("ws.account_notification".to_string()),
"blockNotification" => return Some("ws.block_notification".to_string()),
"logsNotification" => return Some("ws.logs_notification".to_string()),
"programNotification" => return Some("ws.program_notification".to_string()),
"rootNotification" => return Some("ws.root_notification".to_string()),
"signatureNotification" => return Some("ws.signature_notification".to_string()),
"slotNotification" => return Some("ws.slot_notification".to_string()),
"slotsUpdatesNotification" => return Some("ws.slots_updates_notification".to_string()),
"voteNotification" => return Some("ws.vote_notification".to_string()),
_ => return None,
}
}
/// Wraps one raw notification into a normalized JSON payload.
fn build_notification_payload(notification: &crate::KbJsonRpcWsNotification) -> serde_json::Value {
serde_json::json!({
return serde_json::json!({
"jsonrpc": notification.jsonrpc,
"method": notification.method,
"subscription": notification.params.subscription,
"result": notification.params.result,
})
});
}
/// Builds one logical object key from the notification result.
@@ -340,8 +335,7 @@ fn build_object_key(
if let Some(slot) = slot_option {
return format!("slot:{slot}");
}
format!("subscription:{subscription}")
return format!("subscription:{subscription}");
}
/// Extracts a slot number from one notification result.
@@ -364,7 +358,7 @@ fn extract_slot_from_result(method: &str, result: &serde_json::Value) -> std::op
return Some(slot);
}
}
None
return None;
}
/// Extracts a pubkey from one notification result.
@@ -379,7 +373,7 @@ fn extract_pubkey_from_result(
return Some(pubkey.to_string());
}
}
None
return None;
}
/// Extracts a signature from one notification result.
@@ -394,13 +388,13 @@ fn extract_signature_from_result(
return Some(signature.to_string());
}
}
None
return None;
}
/// Extracts one account-like JSON object from one notification result.
fn extract_account_value_from_result<'a>(
result: &'a serde_json::Value,
) -> std::option::Option<&'a serde_json::Value> {
fn extract_account_value_from_result(
result: &serde_json::Value,
) -> std::option::Option<&serde_json::Value> {
if let Some(account) = result.get("account") {
return Some(account);
}
@@ -412,7 +406,7 @@ fn extract_account_value_from_result<'a>(
return Some(value);
}
}
None
return None;
}
/// Extracts the parsed account type from one account-like JSON object.
@@ -431,8 +425,8 @@ fn extract_parsed_account_type(
};
let type_option = parsed.get("type").and_then(serde_json::Value::as_str);
match type_option {
Some(parsed_type) => Some(parsed_type.to_string()),
None => None,
Some(parsed_type) => return Some(parsed_type.to_string()),
None => return None,
}
}
@@ -440,12 +434,10 @@ fn extract_parsed_account_type(
fn extract_account_owner(
account_value: &serde_json::Value,
) -> std::option::Option<std::string::String> {
let owner_option = account_value
.get("owner")
.and_then(serde_json::Value::as_str);
let owner_option = account_value.get("owner").and_then(serde_json::Value::as_str);
match owner_option {
Some(owner) => Some(owner.to_string()),
None => None,
Some(owner) => return Some(owner.to_string()),
None => return None,
}
}
@@ -475,8 +467,8 @@ fn extract_decimals_from_account_value(
};
let converted = u8::try_from(decimals);
match converted {
Ok(decimals) => Some(decimals),
Err(_) => None,
Ok(decimals) => return Some(decimals),
Err(_) => return None,
}
}
@@ -489,7 +481,7 @@ fn extract_program_notification_owner(
Some(account_value) => account_value,
None => return None,
};
extract_account_owner(account_value)
return extract_account_owner(account_value);
}
/// Extracts the parsed token amount decimals from one parsed token account notification.
@@ -516,17 +508,15 @@ fn extract_token_account_decimals_from_account_value(
Some(token_amount) => token_amount,
None => return None,
};
let decimals_option = token_amount
.get("decimals")
.and_then(serde_json::Value::as_u64);
let decimals_option = token_amount.get("decimals").and_then(serde_json::Value::as_u64);
let decimals = match decimals_option {
Some(decimals) => decimals,
None => return None,
};
let convert_result = u8::try_from(decimals);
match convert_result {
Ok(decimals) => Some(decimals),
Err(_) => None,
Ok(decimals) => return Some(decimals),
Err(_) => return None,
}
}
@@ -551,8 +541,8 @@ fn extract_parsed_account_mint(
};
let mint_option = info.get("mint").and_then(serde_json::Value::as_str);
match mint_option {
Some(mint) => Some(mint.to_string()),
None => None,
Some(mint) => return Some(mint.to_string()),
None => return None,
}
}
@@ -580,7 +570,7 @@ fn extract_logs_lines(result: &serde_json::Value) -> std::vec::Vec<std::string::
lines.push(line.to_string());
}
}
lines
return lines;
}
/// Extracts the error field from a signature notification result.
@@ -593,8 +583,8 @@ fn extract_signature_notification_err(
None => return None,
};
match value.get("err") {
Some(err) => Some(err.clone()),
None => None,
Some(err) => return Some(err.clone()),
None => return None,
}
}
@@ -621,8 +611,8 @@ fn build_signal_kind_for_notification(
}
}
}
"signal.account_notification.generic".to_string()
}
return "signal.account_notification.generic".to_string();
},
"logsNotification" => {
let lines = extract_logs_lines(result);
for line in &lines {
@@ -639,21 +629,21 @@ fn build_signal_kind_for_notification(
return "signal.logs_notification.initialize_account".to_string();
}
}
"signal.logs_notification.generic".to_string()
}
return "signal.logs_notification.generic".to_string();
},
"signatureNotification" => {
let err_option = extract_signature_notification_err(result);
match err_option {
Some(err) => {
if err.is_null() {
"signal.signature_notification.confirmed".to_string()
return "signal.signature_notification.confirmed".to_string();
} else {
"signal.signature_notification.failed".to_string()
return "signal.signature_notification.failed".to_string();
}
}
None => "signal.signature_notification.generic".to_string(),
},
None => return "signal.signature_notification.generic".to_string(),
}
}
},
"programNotification" => {
let owner_option = extract_program_notification_owner(result);
let owner = match owner_option {
@@ -666,12 +656,9 @@ fn build_signal_kind_for_notification(
if owner == crate::SPL_TOKEN_2022_PROGRAM_ID.to_string() {
return "signal.program_notification.spl_token_2022".to_string();
}
"signal.program_notification.generic".to_string()
}
_ => format!(
"signal.{}",
method.replace("Notification", "").to_lowercase()
),
return "signal.program_notification.generic".to_string();
},
_ => return format!("signal.{}", method.replace("Notification", "").to_lowercase()),
}
}
@@ -681,8 +668,8 @@ fn build_signal_severity_for_notification(
result: &serde_json::Value,
) -> crate::KbAnalysisSignalSeverity {
match method {
"programNotification" => crate::KbAnalysisSignalSeverity::Medium,
"accountNotification" => crate::KbAnalysisSignalSeverity::Low,
"programNotification" => return crate::KbAnalysisSignalSeverity::Medium,
"accountNotification" => return crate::KbAnalysisSignalSeverity::Low,
"logsNotification" => {
let lines = extract_logs_lines(result);
for line in &lines {
@@ -690,22 +677,22 @@ fn build_signal_severity_for_notification(
return crate::KbAnalysisSignalSeverity::Medium;
}
}
crate::KbAnalysisSignalSeverity::Low
}
return crate::KbAnalysisSignalSeverity::Low;
},
"signatureNotification" => {
let err_option = extract_signature_notification_err(result);
match err_option {
Some(err) => {
if err.is_null() {
crate::KbAnalysisSignalSeverity::Low
return crate::KbAnalysisSignalSeverity::Low;
} else {
crate::KbAnalysisSignalSeverity::Medium
return crate::KbAnalysisSignalSeverity::Medium;
}
}
None => crate::KbAnalysisSignalSeverity::Low,
},
None => return crate::KbAnalysisSignalSeverity::Low,
}
}
_ => crate::KbAnalysisSignalSeverity::Low,
},
_ => return crate::KbAnalysisSignalSeverity::Low,
}
}
@@ -726,13 +713,13 @@ mod tests {
use_wal: true,
},
};
crate::KbDatabase::connect_and_initialize(&config)
return crate::KbDatabase::connect_and_initialize(&config)
.await
.expect("database init must succeed")
.expect("database init must succeed");
}
fn build_slot_notification() -> crate::KbJsonRpcWsNotification {
crate::KbJsonRpcWsNotification {
return crate::KbJsonRpcWsNotification {
jsonrpc: "2.0".to_string(),
method: "slotNotification".to_string(),
params: crate::KbJsonRpcWsNotificationParams {
@@ -743,11 +730,11 @@ mod tests {
}),
subscription: 1008_u64,
},
}
};
}
fn build_program_mint_notification() -> crate::KbJsonRpcWsNotification {
crate::KbJsonRpcWsNotification {
return crate::KbJsonRpcWsNotification {
jsonrpc: "2.0".to_string(),
method: "programNotification".to_string(),
params: crate::KbJsonRpcWsNotificationParams {
@@ -773,11 +760,11 @@ mod tests {
}),
subscription: 2048_u64,
},
}
};
}
fn build_program_pool_candidate_notification() -> crate::KbJsonRpcWsNotification {
crate::KbJsonRpcWsNotification {
return crate::KbJsonRpcWsNotification {
jsonrpc: "2.0".to_string(),
method: "programNotification".to_string(),
params: crate::KbJsonRpcWsNotificationParams {
@@ -798,11 +785,11 @@ mod tests {
}),
subscription: 5555_u64,
},
}
};
}
fn build_logs_notification() -> crate::KbJsonRpcWsNotification {
crate::KbJsonRpcWsNotification {
return crate::KbJsonRpcWsNotification {
jsonrpc: "2.0".to_string(),
method: "logsNotification".to_string(),
params: crate::KbJsonRpcWsNotificationParams {
@@ -820,11 +807,11 @@ mod tests {
}),
subscription: 3001_u64,
},
}
};
}
fn build_signature_notification() -> crate::KbJsonRpcWsNotification {
crate::KbJsonRpcWsNotification {
return crate::KbJsonRpcWsNotification {
jsonrpc: "2.0".to_string(),
method: "signatureNotification".to_string(),
params: crate::KbJsonRpcWsNotificationParams {
@@ -838,7 +825,7 @@ mod tests {
}),
subscription: 4001_u64,
},
}
};
}
#[tokio::test]
@@ -859,7 +846,7 @@ mod tests {
match outcome {
crate::KbSolanaWsDetectionOutcome::ObservationRecorded { observation_id } => {
assert!(observation_id > 0);
}
},
_ => panic!("unexpected detection outcome"),
}
let observations_result =
@@ -894,7 +881,7 @@ mod tests {
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(
@@ -923,14 +910,8 @@ mod tests {
};
assert_eq!(observations.len(), 1);
assert_eq!(signals.len(), 1);
assert_eq!(
observations[0].object_key,
"Mint111111111111111111111111111111111111111"
);
assert_eq!(
signals[0].object_key,
"Mint111111111111111111111111111111111111111"
);
assert_eq!(observations[0].object_key, "Mint111111111111111111111111111111111111111");
assert_eq!(signals[0].object_key, "Mint111111111111111111111111111111111111111");
}
#[tokio::test]
@@ -951,7 +932,7 @@ mod tests {
match outcome {
crate::KbSolanaWsDetectionOutcome::ObservationRecorded { observation_id } => {
assert!(observation_id > 0);
}
},
_ => panic!("unexpected detection outcome"),
}
let observations_result =
@@ -970,10 +951,7 @@ mod tests {
};
assert_eq!(observations.len(), 1);
assert_eq!(signals.len(), 1);
assert_eq!(
signals[0].signal_kind,
"signal.logs_notification.initialize_mint"
);
assert_eq!(signals[0].signal_kind, "signal.logs_notification.initialize_mint");
}
#[tokio::test]
@@ -994,7 +972,7 @@ mod tests {
match outcome {
crate::KbSolanaWsDetectionOutcome::ObservationRecorded { observation_id } => {
assert!(observation_id > 0);
}
},
_ => panic!("unexpected detection outcome"),
}
let observations_result =
@@ -1013,10 +991,7 @@ mod tests {
};
assert_eq!(observations.len(), 1);
assert_eq!(signals.len(), 1);
assert_eq!(
signals[0].signal_kind,
"signal.signature_notification.confirmed"
);
assert_eq!(signals[0].signal_kind, "signal.signature_notification.confirmed");
}
#[tokio::test]
@@ -1053,7 +1028,7 @@ mod tests {
assert!(result.pool_id > 0);
assert!(result.pool_listing_id > 0);
result.pool_id
}
},
_ => panic!("unexpected detection outcome"),
};
let pool_result = crate::get_pool_by_address(

View File

@@ -29,14 +29,14 @@ impl KbDetectionObservationInput {
slot: std::option::Option<u64>,
payload: serde_json::Value,
) -> Self {
Self {
return Self {
observation_kind,
source_kind,
endpoint_name,
object_key,
slot,
payload,
}
};
}
}
@@ -67,14 +67,14 @@ impl KbDetectionSignalInput {
score: std::option::Option<f64>,
payload: serde_json::Value,
) -> Self {
Self {
return Self {
signal_kind,
severity,
object_key,
related_observation_id,
score,
payload,
}
};
}
}
@@ -133,7 +133,7 @@ impl KbDetectionTokenCandidateInput {
signal_score: std::option::Option<f64>,
signal_payload: std::option::Option<serde_json::Value>,
) -> Self {
Self {
return Self {
mint,
symbol,
name,
@@ -149,7 +149,7 @@ impl KbDetectionTokenCandidateInput {
signal_severity,
signal_score,
signal_payload,
}
};
}
}
@@ -207,7 +207,7 @@ impl KbDetectionPoolCandidateInput {
signal_score: std::option::Option<f64>,
signal_payload: std::option::Option<serde_json::Value>,
) -> Self {
Self {
return Self {
pool_address,
dex_program_id,
source_kind,
@@ -219,7 +219,7 @@ impl KbDetectionPoolCandidateInput {
signal_severity,
signal_score,
signal_payload,
}
};
}
}

View File

@@ -20,10 +20,7 @@ impl KbWsDetectionNotificationEnvelope {
endpoint_name: std::option::Option<std::string::String>,
notification: crate::KbJsonRpcWsNotification,
) -> Self {
Self {
endpoint_name,
notification,
}
return Self { endpoint_name, notification };
}
}
@@ -54,7 +51,7 @@ pub struct KbWsDetectionRelay {
impl KbWsDetectionRelay {
/// Creates a new relay.
pub fn new(detector: crate::KbSolanaWsDetectionService) -> Self {
Self { detector }
return Self { detector };
}
/// Creates a bounded relay channel.
@@ -64,7 +61,7 @@ impl KbWsDetectionRelay {
tokio::sync::mpsc::Sender<crate::KbWsDetectionNotificationEnvelope>,
tokio::sync::mpsc::Receiver<crate::KbWsDetectionNotificationEnvelope>,
) {
tokio::sync::mpsc::channel(capacity)
return tokio::sync::mpsc::channel(capacity);
}
/// Processes one forwarded notification.
@@ -72,9 +69,10 @@ impl KbWsDetectionRelay {
&self,
envelope: &crate::KbWsDetectionNotificationEnvelope,
) -> Result<crate::KbSolanaWsDetectionOutcome, crate::KbError> {
self.detector
return self
.detector
.process_notification(envelope.endpoint_name.clone(), &envelope.notification)
.await
.await;
}
/// Spawns one background relay worker.
@@ -82,7 +80,7 @@ impl KbWsDetectionRelay {
self,
mut receiver: tokio::sync::mpsc::Receiver<crate::KbWsDetectionNotificationEnvelope>,
) -> tokio::task::JoinHandle<crate::KbWsDetectionRelayStats> {
tokio::spawn(async move {
return tokio::spawn(async move {
let mut stats = crate::KbWsDetectionRelayStats::default();
loop {
let recv_result = receiver.recv().await;
@@ -103,25 +101,25 @@ impl KbWsDetectionRelay {
error
);
continue;
}
},
};
match outcome {
crate::KbSolanaWsDetectionOutcome::Ignored => {
stats.ignored_count += 1;
}
},
crate::KbSolanaWsDetectionOutcome::ObservationRecorded { .. } => {
stats.observation_count += 1;
}
},
crate::KbSolanaWsDetectionOutcome::TokenCandidateRegistered { .. } => {
stats.token_candidate_count += 1;
}
},
crate::KbSolanaWsDetectionOutcome::PoolCandidateRegistered { .. } => {
stats.pool_candidate_count += 1;
}
},
}
}
stats
})
return stats;
});
}
}
@@ -142,13 +140,13 @@ mod tests {
use_wal: true,
},
};
crate::KbDatabase::connect_and_initialize(&config)
return crate::KbDatabase::connect_and_initialize(&config)
.await
.expect("database init must succeed")
.expect("database init must succeed");
}
fn build_slot_notification() -> crate::KbJsonRpcWsNotification {
crate::KbJsonRpcWsNotification {
return crate::KbJsonRpcWsNotification {
jsonrpc: "2.0".to_string(),
method: "slotNotification".to_string(),
params: crate::KbJsonRpcWsNotificationParams {
@@ -159,7 +157,7 @@ mod tests {
}),
subscription: 1008_u64,
},
}
};
}
#[tokio::test]
@@ -180,7 +178,7 @@ mod tests {
match outcome {
crate::KbSolanaWsDetectionOutcome::ObservationRecorded { observation_id } => {
assert!(observation_id > 0);
}
},
_ => panic!("unexpected relay outcome"),
}
}