Skip to content

Commit 2995a3f

Browse files
committed
Read sourced history pages without local History rows in the B4 simulation
The page comparison passed the store that still holds every History row to get_history_events_from_source, so a direct local read or an accidental fallback would also pass. It now reads through a reader that fails every History column access, and checks that a local read through that reader fails.
1 parent b3a7c8e commit 2995a3f

1 file changed

Lines changed: 71 additions & 2 deletions

File tree

‎server-zd/crates/dexzd-simulation/tests/time_travel_targets.rs‎

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ use dexzd_apply::diagnostics::{
1919
TargetResolutionForTest, TimeTravelTargetForTest, resolve_time_travel_target_for_test,
2020
};
2121
use dexzd_apply::read_model::{get_history_events, get_history_events_from_source};
22-
use dexzd_apply::{ApplyContext, ApplyError, HistoryRecords, HistorySource, StateWrite};
22+
use dexzd_apply::{
23+
ApplyContext, ApplyError, BoundedRowSeek, FlowStateReader, HistoryRecords, HistorySource,
24+
StateReadError, StateRow, StateWrite,
25+
};
2326
use dexzd_keyspace::{ColumnFamily, RunId, RunTable, run_table_prefix};
2427
use dexzd_protocol::dex::{FlowResetStepMethod, FlowResetType, ResetFlowRequest};
2528
use dexzd_protocol::dexzd::v1::{
@@ -233,6 +236,21 @@ impl CheckedFlow<'_> {
233236
continue;
234237
}
235238
let history = MemoryHistorySource::of_run(state, &context, run_id, run);
239+
// The sourced read must take every History row from `history`, never from the store.
240+
let state_without_history = WithoutHistoryColumn { state };
241+
let local_without_history = get_history_events(
242+
&state_without_history,
243+
context.flow_hash,
244+
run_id,
245+
0,
246+
0,
247+
&[],
248+
0,
249+
);
250+
assert!(
251+
local_without_history.is_err(),
252+
"{site}: a local read of run {run_id} passed without History rows"
253+
);
236254
let attempts = [
237255
0,
238256
1,
@@ -255,7 +273,7 @@ impl CheckedFlow<'_> {
255273
panic!("{site}: local history page failed: {error}")
256274
});
257275
let from_source = get_history_events_from_source(
258-
state,
276+
&state_without_history,
259277
&history,
260278
context.flow_hash,
261279
run_id,
@@ -515,6 +533,57 @@ fn strip_target_fields(
515533
stripped
516534
}
517535

536+
/// A reader of `state` that fails every read of the History column family.
537+
struct WithoutHistoryColumn<'state> {
538+
state: &'state MemoryFlowState,
539+
}
540+
541+
impl WithoutHistoryColumn<'_> {
542+
fn reject_history(column_family: ColumnFamily) -> Result<(), StateReadError> {
543+
if column_family == ColumnFamily::History {
544+
return Err(StateReadError {
545+
column_family,
546+
detail: "the sourced read accessed local History rows".to_owned(),
547+
});
548+
}
549+
Ok(())
550+
}
551+
}
552+
553+
impl FlowStateReader for WithoutHistoryColumn<'_> {
554+
fn get(
555+
&self,
556+
column_family: ColumnFamily,
557+
key: &[u8],
558+
) -> Result<Option<Vec<u8>>, StateReadError> {
559+
Self::reject_history(column_family)?;
560+
self.state.get(column_family, key)
561+
}
562+
563+
fn scan(
564+
&self,
565+
column_family: ColumnFamily,
566+
start: &[u8],
567+
end: &[u8],
568+
limit: usize,
569+
) -> Result<Vec<StateRow>, StateReadError> {
570+
Self::reject_history(column_family)?;
571+
self.state.scan(column_family, start, end, limit)
572+
}
573+
574+
fn seek_first_bounded(
575+
&self,
576+
column_family: ColumnFamily,
577+
start: &[u8],
578+
end: &[u8],
579+
max_skipped_deletions: u64,
580+
) -> Result<BoundedRowSeek, StateReadError> {
581+
Self::reject_history(column_family)?;
582+
self.state
583+
.seek_first_bounded(column_family, start, end, max_skipped_deletions)
584+
}
585+
}
586+
518587
/// A history source holding a run's records and checkpoints in memory, as a source built from
519588
/// downloaded history would.
520589
struct MemoryHistorySource {

0 commit comments

Comments
 (0)