This commit is contained in:
2026-04-18 22:42:06 +02:00
parent 03ccc5b3b8
commit d7668c003b
4 changed files with 207 additions and 65 deletions

View File

@@ -8,7 +8,7 @@ members = [
] ]
[workspace.package] [workspace.package]
version = "0.5.2" version = "0.5.3"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-bobot" repository = "https://git.sasedev.com/Sasedev/khadhroony-bobot"

View File

@@ -5,12 +5,16 @@
/// Early heuristic signal derived from classified activity. /// Early heuristic signal derived from classified activity.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum KhbbHeuristicSignal { pub enum KhbbHeuristicSignal {
/// Potential token account creation or initialization activity. /// Potential token account creation or update activity.
PotentialTokenAccountActivity(KhbbPotentialTokenAccountActivitySignal), PotentialTokenAccountActivity(KhbbPotentialTokenAccountActivitySignal),
/// Potential mint-related activity. /// Potential mint-related activity.
PotentialMintActivity(KhbbPotentialMintActivitySignal), PotentialMintActivity(KhbbPotentialMintActivitySignal),
/// Potential initial transaction activity around a token account. /// Potential associated token account activity.
PotentialAssociatedTokenAccountActivity(KhbbPotentialAssociatedTokenAccountActivitySignal),
/// Potential early transaction activity around a token flow.
PotentialInitialTokenActivity(KhbbPotentialInitialTokenActivitySignal), PotentialInitialTokenActivity(KhbbPotentialInitialTokenActivitySignal),
/// Potential token bootstrap activity inferred from known logs/programs.
PotentialTokenBootstrapActivity(KhbbPotentialTokenBootstrapActivitySignal),
} }
/// Heuristic signal indicating potential token account activity. /// Heuristic signal indicating potential token account activity.
@@ -37,6 +41,19 @@ pub struct KhbbPotentialMintActivitySignal {
pub token_program_family: std::string::String, pub token_program_family: std::string::String,
} }
/// Heuristic signal indicating potential associated token account activity.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct KhbbPotentialAssociatedTokenAccountActivitySignal {
/// Account pubkey involved in the signal.
pub pubkey: std::string::String,
/// Context slot.
pub context_slot: u64,
/// Subscription identifier.
pub subscription_id: u64,
/// Token program family.
pub token_program_family: std::string::String,
}
/// Heuristic signal indicating a possibly relevant early transaction activity. /// Heuristic signal indicating a possibly relevant early transaction activity.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct KhbbPotentialInitialTokenActivitySignal { pub struct KhbbPotentialInitialTokenActivitySignal {
@@ -52,68 +69,70 @@ pub struct KhbbPotentialInitialTokenActivitySignal {
pub programs: std::vec::Vec<crate::KhbbKnownProgram>, pub programs: std::vec::Vec<crate::KhbbKnownProgram>,
} }
/// Heuristic signal indicating a likely token bootstrap-related flow.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct KhbbPotentialTokenBootstrapActivitySignal {
/// Transaction signature.
pub signature: std::string::String,
/// Context slot.
pub context_slot: u64,
/// Whether the transaction errored.
pub has_error: bool,
/// Whether a token program was seen.
pub saw_token_program: bool,
/// Whether an associated token program was seen.
pub saw_associated_token_program: bool,
/// Whether the system program was seen.
pub saw_system_program: bool,
/// Whether the compute budget program was seen.
pub saw_compute_budget_program: bool,
}
/// Derives early heuristic signals from classified domain events. /// Derives early heuristic signals from classified domain events.
pub(crate) fn derive_heuristic_signals( pub(crate) fn derive_heuristic_signals(
event: &crate::KhbbClassifiedDomainEvent, event: &crate::KhbbClassifiedDomainEvent,
) -> core::result::Result<std::vec::Vec<KhbbHeuristicSignal>, crate::KhbbError> { ) -> core::result::Result<std::vec::Vec<KhbbHeuristicSignal>, crate::KhbbError> {
match event { match event {
crate::KhbbClassifiedDomainEvent::SplTokenProgramActivity(activity) => { crate::KhbbClassifiedDomainEvent::SplTokenProgramActivity(activity) => {
let mut signals = std::vec::Vec::<KhbbHeuristicSignal>::new(); derive_token_program_activity_signals(
signals.push(KhbbHeuristicSignal::PotentialTokenAccountActivity( activity.subscription_id,
KhbbPotentialTokenAccountActivitySignal { &activity.pubkey,
pubkey: activity.pubkey.clone(), activity.context_slot,
context_slot: activity.context_slot, "spl-token",
subscription_id: activity.subscription_id, )
token_program_family: std::string::String::from("spl-token"),
}, },
));
if activity.pubkey != crate::ids::WSOL_MINT_ID.to_string() {
signals.push(KhbbHeuristicSignal::PotentialMintActivity(
KhbbPotentialMintActivitySignal {
pubkey: activity.pubkey.clone(),
context_slot: activity.context_slot,
token_program_family: std::string::String::from("spl-token"),
},
));
}
Ok(signals)
}
crate::KhbbClassifiedDomainEvent::SplToken2022ProgramActivity(activity) => { crate::KhbbClassifiedDomainEvent::SplToken2022ProgramActivity(activity) => {
let mut signals = std::vec::Vec::<KhbbHeuristicSignal>::new(); derive_token_program_activity_signals(
signals.push(KhbbHeuristicSignal::PotentialTokenAccountActivity( activity.subscription_id,
KhbbPotentialTokenAccountActivitySignal { &activity.pubkey,
pubkey: activity.pubkey.clone(), activity.context_slot,
context_slot: activity.context_slot, "spl-token-2022",
subscription_id: activity.subscription_id, )
token_program_family: std::string::String::from("spl-token-2022"),
}, },
));
signals.push(KhbbHeuristicSignal::PotentialMintActivity(
KhbbPotentialMintActivitySignal {
pubkey: activity.pubkey.clone(),
context_slot: activity.context_slot,
token_program_family: std::string::String::from("spl-token-2022"),
},
));
Ok(signals)
}
crate::KhbbClassifiedDomainEvent::KnownProgramLogActivity(activity) => { crate::KhbbClassifiedDomainEvent::KnownProgramLogActivity(activity) => {
let mut contains_token_program = false; let mut signals = std::vec::Vec::<KhbbHeuristicSignal>::new();
let mut saw_token_program = false;
let mut saw_associated_token_program = false;
let mut saw_system_program = false;
let mut saw_compute_budget_program = false;
for program in &activity.programs { for program in &activity.programs {
match program { match program {
crate::KhbbKnownProgram::SplToken crate::KhbbKnownProgram::SplToken | crate::KhbbKnownProgram::SplToken2022 => {
| crate::KhbbKnownProgram::SplToken2022 saw_token_program = true;
| crate::KhbbKnownProgram::AssociatedTokenAccount => { },
contains_token_program = true; crate::KhbbKnownProgram::AssociatedTokenAccount => {
break; saw_associated_token_program = true;
} },
_ => {} crate::KhbbKnownProgram::System => {
saw_system_program = true;
},
crate::KhbbKnownProgram::ComputeBudget => {
saw_compute_budget_program = true;
},
} }
} }
if !contains_token_program { if saw_token_program || saw_associated_token_program {
return Ok(vec![]); signals.push(KhbbHeuristicSignal::PotentialInitialTokenActivity(
}
Ok(vec![KhbbHeuristicSignal::PotentialInitialTokenActivity(
KhbbPotentialInitialTokenActivitySignal { KhbbPotentialInitialTokenActivitySignal {
signature: activity.signature.clone(), signature: activity.signature.clone(),
context_slot: activity.context_slot, context_slot: activity.context_slot,
@@ -121,12 +140,68 @@ pub(crate) fn derive_heuristic_signals(
log_count: activity.programs.len(), log_count: activity.programs.len(),
programs: activity.programs.clone(), programs: activity.programs.clone(),
}, },
)]) ));
} }
if saw_token_program && (saw_associated_token_program || saw_system_program) {
signals.push(KhbbHeuristicSignal::PotentialTokenBootstrapActivity(
KhbbPotentialTokenBootstrapActivitySignal {
signature: activity.signature.clone(),
context_slot: activity.context_slot,
has_error: activity.has_error,
saw_token_program,
saw_associated_token_program,
saw_system_program,
saw_compute_budget_program,
},
));
}
Ok(signals)
},
crate::KhbbClassifiedDomainEvent::UnknownProgramLogActivity(_) => Ok(vec![]), crate::KhbbClassifiedDomainEvent::UnknownProgramLogActivity(_) => Ok(vec![]),
} }
} }
fn derive_token_program_activity_signals(
subscription_id: u64,
pubkey: &str,
context_slot: u64,
token_program_family: &str,
) -> core::result::Result<std::vec::Vec<KhbbHeuristicSignal>, crate::KhbbError> {
let mut signals = std::vec::Vec::<KhbbHeuristicSignal>::new();
signals.push(KhbbHeuristicSignal::PotentialTokenAccountActivity(
KhbbPotentialTokenAccountActivitySignal {
pubkey: std::string::String::from(pubkey),
context_slot,
subscription_id,
token_program_family: std::string::String::from(token_program_family),
},
));
let wsol_text = crate::ids::WSOL_MINT_ID.to_string();
if pubkey != wsol_text {
signals.push(KhbbHeuristicSignal::PotentialMintActivity(KhbbPotentialMintActivitySignal {
pubkey: std::string::String::from(pubkey),
context_slot,
token_program_family: std::string::String::from(token_program_family),
}));
}
if looks_like_associated_token_account(pubkey) {
signals.push(KhbbHeuristicSignal::PotentialAssociatedTokenAccountActivity(
KhbbPotentialAssociatedTokenAccountActivitySignal {
pubkey: std::string::String::from(pubkey),
context_slot,
subscription_id,
token_program_family: std::string::String::from(token_program_family),
},
));
}
Ok(signals)
}
fn looks_like_associated_token_account(pubkey: &str) -> bool {
let length = pubkey.len();
length >= 32 && length <= 44
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
#[test] #[test]
@@ -141,11 +216,11 @@ mod tests {
let result = super::derive_heuristic_signals(&event); let result = super::derive_heuristic_signals(&event);
assert!(result.is_ok()); assert!(result.is_ok());
let signals = result.expect("derive spl-token signals"); let signals = result.expect("derive spl-token signals");
assert_eq!(signals.len(), 2); assert!(signals.len() >= 2);
} }
#[test] #[test]
fn derive_heuristics_from_known_program_logs_returns_signal_when_token_program_seen() { fn derive_heuristics_from_known_program_logs_returns_signals_when_token_program_seen() {
let event = crate::KhbbClassifiedDomainEvent::KnownProgramLogActivity( let event = crate::KhbbClassifiedDomainEvent::KnownProgramLogActivity(
crate::KhbbKnownProgramLogActivityEvent { crate::KhbbKnownProgramLogActivityEvent {
signature: std::string::String::from("sig-1"), signature: std::string::String::from("sig-1"),
@@ -163,11 +238,31 @@ mod tests {
assert_eq!(signals.len(), 1); assert_eq!(signals.len(), 1);
} }
#[test]
fn derive_heuristics_from_known_program_logs_returns_bootstrap_signal_when_system_seen() {
let event = crate::KhbbClassifiedDomainEvent::KnownProgramLogActivity(
crate::KhbbKnownProgramLogActivityEvent {
signature: std::string::String::from("sig-2"),
context_slot: 201,
has_error: false,
programs: vec![
crate::KhbbKnownProgram::ComputeBudget,
crate::KhbbKnownProgram::System,
crate::KhbbKnownProgram::SplToken,
],
},
);
let result = super::derive_heuristic_signals(&event);
assert!(result.is_ok());
let signals = result.expect("derive bootstrap signals");
assert_eq!(signals.len(), 2);
}
#[test] #[test]
fn derive_heuristics_from_unknown_program_logs_returns_no_signal() { fn derive_heuristics_from_unknown_program_logs_returns_no_signal() {
let event = crate::KhbbClassifiedDomainEvent::UnknownProgramLogActivity( let event = crate::KhbbClassifiedDomainEvent::UnknownProgramLogActivity(
crate::KhbbUnknownProgramLogActivityEvent { crate::KhbbUnknownProgramLogActivityEvent {
signature: std::string::String::from("sig-2"), signature: std::string::String::from("sig-3"),
context_slot: 300, context_slot: 300,
has_error: false, has_error: false,
log_count: 2, log_count: 2,
@@ -178,4 +273,11 @@ mod tests {
let signals = result.expect("derive unknown log signals"); let signals = result.expect("derive unknown log signals");
assert!(signals.is_empty()); assert!(signals.is_empty());
} }
#[test]
fn looks_like_associated_token_account_accepts_base58_like_length() {
assert!(super::looks_like_associated_token_account(
"24Ux1iXBGBgx83SNX9SZkSkwxDLsK1HjXhcuMMsNduVn"
));
}
} }

View File

@@ -109,3 +109,7 @@ pub use crate::heuristics::KhbbPotentialTokenAccountActivitySignal;
pub use crate::heuristics::KhbbPotentialMintActivitySignal; pub use crate::heuristics::KhbbPotentialMintActivitySignal;
/// Heuristic signal indicating a possibly relevant early transaction activity. /// Heuristic signal indicating a possibly relevant early transaction activity.
pub use crate::heuristics::KhbbPotentialInitialTokenActivitySignal; pub use crate::heuristics::KhbbPotentialInitialTokenActivitySignal;
/// Heuristic signal indicating potential associated token account activity.
pub use crate::heuristics::KhbbPotentialAssociatedTokenAccountActivitySignal;
/// Heuristic signal indicating a likely token bootstrap-related flow.
pub use crate::heuristics::KhbbPotentialTokenBootstrapActivitySignal;

View File

@@ -479,8 +479,22 @@ pub async fn run_listener_runtime(
"heuristic potential initial token activity signal" "heuristic potential initial token activity signal"
); );
} }
crate::KhbbHeuristicSignal::PotentialTokenBootstrapActivity(inner) => {
tracing::trace!(
listener_session_id = session.id,
signature = %inner.signature,
context_slot = inner.context_slot,
has_error = inner.has_error,
saw_token_program = inner.saw_token_program,
saw_associated_token_program = inner.saw_associated_token_program,
saw_system_program = inner.saw_system_program,
saw_compute_budget_program = inner.saw_compute_budget_program,
"heuristic potential token bootstrap activity signal"
);
}
crate::KhbbHeuristicSignal::PotentialTokenAccountActivity(_) => {} crate::KhbbHeuristicSignal::PotentialTokenAccountActivity(_) => {}
crate::KhbbHeuristicSignal::PotentialMintActivity(_) => {} crate::KhbbHeuristicSignal::PotentialMintActivity(_) => {}
crate::KhbbHeuristicSignal::PotentialAssociatedTokenAccountActivity(_) => {}
} }
} }
} }
@@ -569,7 +583,18 @@ pub async fn run_listener_runtime(
"heuristic potential mint activity signal" "heuristic potential mint activity signal"
); );
} }
crate::KhbbHeuristicSignal::PotentialAssociatedTokenAccountActivity(inner) => {
tracing::trace!(
listener_session_id = session.id,
pubkey = %inner.pubkey,
context_slot = inner.context_slot,
subscription_id = inner.subscription_id,
token_program_family = %inner.token_program_family,
"heuristic potential associated token account activity signal"
);
}
crate::KhbbHeuristicSignal::PotentialInitialTokenActivity(_) => {} crate::KhbbHeuristicSignal::PotentialInitialTokenActivity(_) => {}
crate::KhbbHeuristicSignal::PotentialTokenBootstrapActivity(_) => {}
} }
} }
} }
@@ -619,7 +644,18 @@ pub async fn run_listener_runtime(
"heuristic potential mint activity signal" "heuristic potential mint activity signal"
); );
} }
crate::KhbbHeuristicSignal::PotentialAssociatedTokenAccountActivity(inner) => {
tracing::trace!(
listener_session_id = session.id,
pubkey = %inner.pubkey,
context_slot = inner.context_slot,
subscription_id = inner.subscription_id,
token_program_family = %inner.token_program_family,
"heuristic potential associated token account activity signal"
);
}
crate::KhbbHeuristicSignal::PotentialInitialTokenActivity(_) => {} crate::KhbbHeuristicSignal::PotentialInitialTokenActivity(_) => {}
crate::KhbbHeuristicSignal::PotentialTokenBootstrapActivity(_) => {}
} }
} }
} }