0.5.3
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user