0.7.47-1FE5

This commit is contained in:
2026-05-31 16:43:19 +02:00
parent 7bd6593015
commit 8b09e82b3b
39 changed files with 24260 additions and 332 deletions

View File

@@ -20,6 +20,8 @@ pub struct DexDecodeService {
meteora_damm_v2_decoder: crate::MeteoraDammV2Decoder,
fluxbeam_decoder: crate::FluxbeamDecoder,
dexlab_decoder: crate::DexlabDecoder,
openbook_v2_decoder: crate::OpenBookV2Decoder,
phoenix_v1_decoder: crate::PhoenixV1Decoder,
}
impl DexDecodeService {
@@ -40,6 +42,8 @@ impl DexDecodeService {
meteora_damm_v2_decoder: crate::MeteoraDammV2Decoder::new(),
fluxbeam_decoder: crate::FluxbeamDecoder::new(),
dexlab_decoder: crate::DexlabDecoder::new(),
openbook_v2_decoder: crate::OpenBookV2Decoder::new(),
phoenix_v1_decoder: crate::PhoenixV1Decoder::new(),
};
}
@@ -163,6 +167,36 @@ impl DexDecodeService {
if let Err(error) = append_result {
return Err(error);
}
let append_result = append_persisted_events_result(
&mut persisted,
self.decode_and_persist_openbook_v2_audit_events(&transaction, &instructions)
.await,
);
if let Err(error) = append_result {
return Err(error);
}
let append_result = append_persisted_events_result(
&mut persisted,
self.decode_and_persist_phoenix_v1_audit_events(&transaction, &instructions)
.await,
);
if let Err(error) = append_result {
return Err(error);
}
let decoded_instruction_ids = decoded_instruction_ids_from_persisted_events(&persisted);
let append_result = append_persisted_events_result(
&mut persisted,
self.decode_and_persist_upstream_registry_matches(
&transaction,
&instructions,
&decoded_instruction_ids,
)
.await,
);
if let Err(error) = append_result {
return Err(error);
}
return Ok(persisted);
}
@@ -218,9 +252,46 @@ impl DexDecodeService {
if let Err(error) = cleanup_result {
return Err(error);
}
let cleanup_result = self
.delete_replaced_upstream_registry_match(
transaction_id,
instruction_id,
protocol_name,
event_kind,
)
.await;
if let Err(error) = cleanup_result {
return Err(error);
}
return Ok(materialized);
}
async fn delete_replaced_upstream_registry_match(
&self,
transaction_id: i64,
instruction_id: i64,
protocol_name: &str,
event_kind: &str,
) -> Result<(), crate::Error> {
if protocol_name == crate::UPSTREAM_REGISTRY_PROTOCOL_NAME {
return Ok(());
}
if event_kind == crate::UPSTREAM_REGISTRY_INSTRUCTION_MATCH_EVENT_KIND {
return Ok(());
}
let delete_result = crate::query_dex_decoded_events_delete_by_key(
self.database.as_ref(),
transaction_id,
Some(instruction_id),
crate::UPSTREAM_REGISTRY_INSTRUCTION_MATCH_EVENT_KIND,
)
.await;
match delete_result {
Ok(_) => return Ok(()),
Err(error) => return Err(error),
}
}
async fn delete_replaced_instruction_audit(
&self,
transaction_id: i64,
@@ -248,6 +319,92 @@ impl DexDecodeService {
}
}
async fn decode_and_persist_upstream_registry_matches(
&self,
transaction: &crate::ChainTransactionDto,
instructions: &[crate::ChainInstructionDto],
already_decoded_instruction_ids: &std::collections::HashSet<i64>,
) -> Result<std::vec::Vec<crate::DexDecodedEventDto>, crate::Error> {
let transaction_id = match transaction.id {
Some(transaction_id) => transaction_id,
None => {
return Err(crate::Error::InvalidState(format!(
"transaction '{}' has no internal id",
transaction.signature
)));
},
};
let decoded_events_result = crate::query_dex_decoded_events_list_by_transaction_id(
self.database.as_ref(),
transaction_id,
)
.await;
let decoded_events = match decoded_events_result {
Ok(decoded_events) => decoded_events,
Err(error) => return Err(error),
};
let mut decoded_instruction_ids = already_decoded_instruction_ids.clone();
for decoded_event in &decoded_events {
let instruction_id = match decoded_event.instruction_id {
Some(instruction_id) => instruction_id,
None => continue,
};
decoded_instruction_ids.insert(instruction_id);
}
let mut persisted = std::vec::Vec::new();
for instruction in instructions {
let instruction_id = match instruction.id {
Some(instruction_id) => instruction_id,
None => continue,
};
if decoded_instruction_ids.contains(&instruction_id) {
continue;
}
let program_id = match instruction.program_id.as_ref() {
Some(program_id) => program_id,
None => continue,
};
let data_base58 = parse_instruction_data_base58(instruction.data_json.as_deref());
let registry_match =
crate::upstream_registry_match::upstream_registry_match_instruction_data(
program_id.as_str(),
data_base58.as_deref(),
);
let registry_match = match registry_match {
Some(registry_match) => registry_match,
None => continue,
};
let payload = build_upstream_registry_instruction_match_payload(
transaction,
instruction,
&registry_match,
data_base58.as_deref(),
);
let persist_result = self
.materialize_named_dex_event(
transaction,
transaction_id,
instruction_id,
crate::UPSTREAM_REGISTRY_PROTOCOL_NAME,
program_id.clone(),
crate::UPSTREAM_REGISTRY_INSTRUCTION_MATCH_EVENT_KIND,
None,
None,
None,
None,
None,
payload,
)
.await;
let persisted_event = match persist_result {
Ok(persisted_event) => persisted_event,
Err(error) => return Err(error),
};
persisted.push(persisted_event);
}
return Ok(persisted);
}
async fn persist_dexlab_event(
&self,
transaction: &crate::ChainTransactionDto,
@@ -1480,6 +1637,104 @@ impl DexDecodeService {
return Ok(persisted);
}
async fn persist_openbook_v2_event(
&self,
transaction: &crate::ChainTransactionDto,
decoded_event: &crate::OpenBookV2DecodedEvent,
) -> Result<crate::DexDecodedEventDto, crate::Error> {
match decoded_event {
crate::OpenBookV2DecodedEvent::Audit(event) => {
return self
.materialize_named_dex_event(
transaction,
event.transaction_id,
event.instruction_id,
"openbook_v2",
event.program_id.clone(),
event.event_kind.as_str(),
None,
event.market_account.clone(),
event.token_a_mint.clone(),
event.token_b_mint.clone(),
None,
event.payload_json.clone(),
)
.await;
},
}
}
async fn decode_and_persist_openbook_v2_audit_events(
&self,
transaction: &crate::ChainTransactionDto,
instructions: &[crate::ChainInstructionDto],
) -> Result<std::vec::Vec<crate::DexDecodedEventDto>, crate::Error> {
let decoded_result = self.openbook_v2_decoder.decode_transaction(transaction, instructions);
let decoded_events = match decoded_result {
Ok(decoded_events) => decoded_events,
Err(error) => return Err(error),
};
let mut persisted = std::vec::Vec::new();
for decoded_event in &decoded_events {
let persist_result = self.persist_openbook_v2_event(transaction, decoded_event).await;
let persisted_event = match persist_result {
Ok(persisted_event) => persisted_event,
Err(error) => return Err(error),
};
persisted.push(persisted_event);
}
return Ok(persisted);
}
async fn persist_phoenix_v1_event(
&self,
transaction: &crate::ChainTransactionDto,
decoded_event: &crate::PhoenixV1DecodedEvent,
) -> Result<crate::DexDecodedEventDto, crate::Error> {
match decoded_event {
crate::PhoenixV1DecodedEvent::Audit(event) => {
return self
.materialize_named_dex_event(
transaction,
event.transaction_id,
event.instruction_id,
"phoenix_v1",
event.program_id.clone(),
event.event_kind.as_str(),
None,
event.market_account.clone(),
event.token_a_mint.clone(),
event.token_b_mint.clone(),
None,
event.payload_json.clone(),
)
.await;
},
}
}
async fn decode_and_persist_phoenix_v1_audit_events(
&self,
transaction: &crate::ChainTransactionDto,
instructions: &[crate::ChainInstructionDto],
) -> Result<std::vec::Vec<crate::DexDecodedEventDto>, crate::Error> {
let decoded_result = self.phoenix_v1_decoder.decode_transaction(transaction, instructions);
let decoded_events = match decoded_result {
Ok(decoded_events) => decoded_events,
Err(error) => return Err(error),
};
let mut persisted = std::vec::Vec::new();
for decoded_event in &decoded_events {
let persist_result = self.persist_phoenix_v1_event(transaction, decoded_event).await;
let persisted_event = match persist_result {
Ok(persisted_event) => persisted_event,
Err(error) => return Err(error),
};
persisted.push(persisted_event);
}
return Ok(persisted);
}
async fn decode_and_persist_fluxbeam_events(
&self,
transaction: &crate::ChainTransactionDto,
@@ -1964,7 +2219,7 @@ fn build_meteora_instruction_audit_payload(
return serde_json::json!({
"decoder": protocol_name,
"eventKind": event_kind,
"signature": transaction.signature,
"signature": transaction.signature.clone(),
"instructionId": instruction.id,
"instructionIndex": instruction.instruction_index,
"innerInstructionIndex": instruction.inner_instruction_index,
@@ -2023,7 +2278,7 @@ fn build_raydium_instruction_audit_payload(
return serde_json::json!({
"decoder": protocol_name,
"eventKind": event_kind,
"signature": transaction.signature,
"signature": transaction.signature.clone(),
"instructionId": instruction.id,
"instructionIndex": instruction.instruction_index,
"innerInstructionIndex": instruction.inner_instruction_index,
@@ -2073,6 +2328,58 @@ fn candidate_raydium_audit_pool_account(
return accounts.get(spec.candidate_pool_account_index).cloned();
}
fn build_upstream_registry_instruction_match_payload(
transaction: &crate::ChainTransactionDto,
instruction: &crate::ChainInstructionDto,
registry_match: &crate::UpstreamRegistryEntryDto,
data_base58: std::option::Option<&str>,
) -> serde_json::Value {
let data_bytes = instruction_data_bytes_from_base58(data_base58);
let data_byte_len = match data_bytes.as_ref() {
Some(data_bytes) => {
let len_result = u64::try_from(data_bytes.len());
match len_result {
Ok(len) => serde_json::Value::Number(serde_json::Number::from(len)),
Err(_) => serde_json::Value::Null,
}
},
None => serde_json::Value::Null,
};
return serde_json::json!({
"decoder": crate::UPSTREAM_REGISTRY_PROTOCOL_NAME,
"matchKind": "instruction_discriminator",
"signature": transaction.signature.clone(),
"slot": transaction.slot,
"instructionId": instruction.id,
"instructionIndex": instruction.instruction_index,
"innerInstructionIndex": instruction.inner_instruction_index,
"parentInstructionId": instruction.parent_instruction_id,
"stackHeight": instruction.stack_height,
"programId": instruction.program_id.clone(),
"programName": instruction.program_name.clone(),
"accounts": parse_instruction_accounts_value(instruction.accounts_json.as_str()),
"accountCount": parse_instruction_accounts_vec(instruction.accounts_json.as_str()).len(),
"dataBase58": data_base58,
"dataByteLen": data_byte_len,
"upstreamSourceRepo": registry_match.source_repo.clone(),
"upstreamSourcePath": registry_match.source_path.clone(),
"upstreamDecoderCode": registry_match.decoder_code.clone(),
"upstreamProgramFamily": registry_match.program_family.clone(),
"upstreamSurfaceKind": registry_match.surface_kind.clone(),
"upstreamEntryKind": registry_match.entry_kind.clone(),
"upstreamEntryName": registry_match.entry_name.clone(),
"upstreamDiscriminatorHex": registry_match.discriminator_hex.clone(),
"upstreamDiscriminatorLen": registry_match.discriminator_len,
"upstreamProofStatus": registry_match.proof_status.clone(),
"upstreamNotes": registry_match.notes.clone(),
"tradeCandidate": false,
"candleCandidate": false,
"nonTradeUseful": false,
"skipTradeReason": "upstream_git_unverified_registry_match",
"skipCandleReason": "upstream_git_unverified_registry_match"
});
}
fn parse_instruction_accounts_vec(accounts_json: &str) -> std::vec::Vec<std::string::String> {
let accounts_result = serde_json::from_str::<std::vec::Vec<std::string::String>>(accounts_json);
match accounts_result {
@@ -2146,6 +2453,20 @@ fn append_persisted_events(
}
}
fn decoded_instruction_ids_from_persisted_events(
persisted: &[crate::DexDecodedEventDto],
) -> std::collections::HashSet<i64> {
let mut decoded_instruction_ids = std::collections::HashSet::<i64>::new();
for decoded_event in persisted {
let instruction_id = match decoded_event.instruction_id {
Some(instruction_id) => instruction_id,
None => continue,
};
decoded_instruction_ids.insert(instruction_id);
}
return decoded_instruction_ids;
}
fn append_persisted_events_result(
target: &mut std::vec::Vec<crate::DexDecodedEventDto>,
source_result: Result<std::vec::Vec<crate::DexDecodedEventDto>, crate::Error>,
@@ -3223,4 +3544,60 @@ mod tests {
);
assert_eq!(super::instruction_audit_event_kind_by_protocol("unknown"), None);
}
#[test]
fn upstream_registry_match_payload_is_never_trade_or_candle_candidate() {
let transaction = crate::ChainTransactionDto::new(
"upstream-registry-test-signature".to_string(),
Some(123),
Some(123456),
Some("test".to_string()),
None,
None,
None,
"{}".to_string(),
);
let instruction = crate::ChainInstructionDto::new(
1,
None,
0,
None,
Some(crate::METEORA_DAMM_V2_PROGRAM_ID.to_string()),
None,
None,
"[]".to_string(),
Some("data".to_string()),
None,
None,
);
let registry_match = crate::UpstreamRegistryEntryDto {
source_repo: Some("sevenlabs-hq/carbon".to_string()),
source_path: Some("decoders/example.rs".to_string()),
decoder_code: "meteora-damm-v2".to_string(),
program_id: Some(crate::METEORA_DAMM_V2_PROGRAM_ID.to_string()),
program_family: "meteora".to_string(),
surface_kind: "amm".to_string(),
entry_kind: crate::ENTRY_KIND_INSTRUCTION.to_string(),
entry_name: "swap".to_string(),
discriminator_hex: Some("f8c69e91e17587c8".to_string()),
discriminator_len: Some(8),
proof_status: crate::PROOF_STATUS_UPSTREAM_GIT_UNVERIFIED.to_string(),
notes: "test".to_string(),
};
let payload = super::build_upstream_registry_instruction_match_payload(
&transaction,
&instruction,
&registry_match,
Some("data"),
);
assert_eq!(payload.get("tradeCandidate").and_then(serde_json::Value::as_bool), Some(false));
assert_eq!(
payload.get("candleCandidate").and_then(serde_json::Value::as_bool),
Some(false)
);
assert_eq!(
payload.get("upstreamProofStatus").and_then(serde_json::Value::as_str),
Some(crate::PROOF_STATUS_UPSTREAM_GIT_UNVERIFIED)
);
}
}