fix events being dropped in streaming ordered mode for multi event transactions

in StreamingOrdered mode the slot buffer is fed one event at a time, but every
event parsed from the same transaction carries that transaction's tx_index.
push_streaming bumps the streaming watermark to tx_index + 1 as soon as it
releases the first event, so the rest of that transaction's events have
tx_index < watermark, fall into the "already delivered" branch and get dropped
without any trace.

so any transaction that parses into more than one dex event (a swap route that
touches several pools, a create + buy, and so on) only ever delivered its first
event when order_mode was StreamingOrdered. Unordered, Ordered and MicroBatch
were not affected.

fix:
- push_streaming now takes all events of one (slot, tx_index) as a group so the
  watermark advances once per transaction instead of once per event
- the buffered drain advances the watermark per distinct tx_index, since a slot
  can now hold several events under the same index
- the ordered buffers use a stable sort so events inside one transaction keep
  the order the parser produced them in, sort_unstable could shuffle equal keys

added tests for the multi event transaction case and for releasing buffered out
of order multi event transactions.
This commit is contained in:
moondev
2026-06-16 00:28:15 +09:00
parent 0a879cac05
commit 94cd09b0e5
2 changed files with 103 additions and 20 deletions
+26 -3
View File
@@ -464,9 +464,13 @@ fn handle_ordered_transaction(
}
}
OrderMode::StreamingOrdered => {
for event in events {
let (slot, tx_index) = event_order_key(&event, fallback_slot, fallback_tx_index);
deliver_events(callback.clone(), slot_buffer.push_streaming(slot, tx_index, event));
// Events parsed from one transaction share its (slot, tx_index); push them as a
// single group so the streaming watermark advances per transaction and no event of
// a multi-event transaction is dropped.
for ((slot, tx_index), group) in
group_events_by_order_key(events, fallback_slot, fallback_tx_index)
{
deliver_events(callback.clone(), slot_buffer.push_streaming(slot, tx_index, group));
}
}
OrderMode::MicroBatch => {
@@ -541,6 +545,25 @@ fn event_order_key(event: &DexEvent, fallback_slot: u64, fallback_tx_index: u64)
(metadata.slot.max(fallback_slot), metadata.tx_index.unwrap_or(fallback_tx_index))
}
/// Group consecutive events that share the same `(slot, tx_index)` order key, preserving order.
/// Events of a single transaction carry the same key, so this normally yields one group, but it
/// stays correct if a transaction ever spans multiple keys.
fn group_events_by_order_key(
events: Vec<DexEvent>,
fallback_slot: u64,
fallback_tx_index: u64,
) -> Vec<((u64, u64), Vec<DexEvent>)> {
let mut groups: Vec<((u64, u64), Vec<DexEvent>)> = Vec::new();
for event in events {
let key = event_order_key(&event, fallback_slot, fallback_tx_index);
match groups.last_mut() {
Some((last_key, group)) if *last_key == key => group.push(event),
_ => groups.push((key, vec![event])),
}
}
groups
}
#[inline]
fn deliver_events(callback: Arc<dyn Fn(DexEvent) + Send + Sync>, events: Vec<DexEvent>) {
for event in events {