diff --git a/processor/scanner/src/db.rs b/processor/scanner/src/db.rs index e92435bc..70e34315 100644 --- a/processor/scanner/src/db.rs +++ b/processor/scanner/src/db.rs @@ -33,6 +33,8 @@ create_db!( NextToCheckForEventualitiesBlock: () -> u64, // The next block to potentially report NextToPotentiallyReportBlock: () -> u64, + // Highest acknowledged block + HighestAcknowledgedBlock: () -> u64, // If a block was notable /* @@ -122,7 +124,9 @@ impl ScannerDb { Self::set_block(txn, start_block, id); LatestFinalizedBlock::set(txn, &start_block); NextToScanForOutputsBlock::set(txn, &start_block); - NextToCheckForEventualitiesBlock::set(txn, &start_block); + // We can receive outputs in this block, but any descending transactions will be in the next + // block. This, with the check on-set, creates a bound that this value in the DB is non-zero. + NextToCheckForEventualitiesBlock::set(txn, &(start_block + 1)); NextToPotentiallyReportBlock::set(txn, &start_block); } @@ -153,6 +157,10 @@ impl ScannerDb { txn: &mut impl DbTxn, next_to_check_for_eventualities_block: u64, ) { + assert!( + next_to_check_for_eventualities_block != 0, + "next to check for eventualities block was 0 when it's bound non-zero" + ); NextToCheckForEventualitiesBlock::set(txn, &next_to_check_for_eventualities_block); } pub(crate) fn next_to_check_for_eventualities_block(getter: &impl Get) -> Option { @@ -169,6 +177,16 @@ impl ScannerDb { NextToPotentiallyReportBlock::get(getter) } + pub(crate) fn set_highest_acknowledged_block( + txn: &mut impl DbTxn, + highest_acknowledged_block: u64, + ) { + HighestAcknowledgedBlock::set(txn, &highest_acknowledged_block); + } + pub(crate) fn highest_acknowledged_block(getter: &impl Get) -> Option { + HighestAcknowledgedBlock::get(getter) + } + pub(crate) fn set_outputs(txn: &mut impl DbTxn, block_number: u64, outputs: Vec>) { if outputs.is_empty() { return; diff --git a/processor/scanner/src/eventuality.rs b/processor/scanner/src/eventuality.rs index 38f1d112..37892aa8 100644 --- a/processor/scanner/src/eventuality.rs +++ b/processor/scanner/src/eventuality.rs @@ -1,4 +1,9 @@ -// TODO +use serai_db::{Db, DbTxn}; + +use primitives::{Id, ReceivedOutput, Block}; + +// TODO: Localize to EventualityDb? +use crate::{db::ScannerDb, ScannerFeed, ContinuallyRan}; /* Note: The following assumes there's some value, `CONFIRMATIONS`, and the finalized block we @@ -48,3 +53,71 @@ This forms a backlog only if the latency of scanning, acknowledgement, and intake (including checking Eventualities) exceeds the window duration (the desired property). */ +struct EventualityTask { + db: D, + feed: S, +} + +#[async_trait::async_trait] +impl ContinuallyRan for EventualityTask { + async fn run_iteration(&mut self) -> Result { + /* + The set of Eventualities only increase when a block is acknowledged. Accordingly, we can only + iterate up to (and including) the block currently pending acknowledgement. "including" is + because even if block `b` causes new Eventualities, they'll only potentially resolve in block + `b + 1`. + + We only know blocks will need acknowledgement *for sure* if they were scanned. The only other + causes are key activation and retirement (both scheduled outside the scan window). This makes + the exclusive upper bound the *next block to scan*. + */ + let exclusive_upper_bound = { + // Fetch the next to scan block + let next_to_scan = ScannerDb::::next_to_scan_for_outputs_block(&self.db) + .expect("EventualityTask run before writing the start block"); + // If we haven't done any work, return + if next_to_scan == 0 { + return Ok(false); + } + next_to_scan + }; + + // Fetch the highest acknowledged block + let highest_acknowledged = ScannerDb::::highest_acknowledged_block(&self.db) + .expect("EventualityTask run before writing the start block"); + + // Fetch the next block to check + let next_to_check = ScannerDb::::next_to_check_for_eventualities_block(&self.db) + .expect("EventualityTask run before writing the start block"); + + // Check all blocks + let mut iterated = false; + for b in next_to_check .. exclusive_upper_bound { + // If the prior block was notable *and* not acknowledged, break + // This is so if it caused any Eventualities (which may resolve this block), we have them + { + // This `- 1` is safe as next to check is bound to be non-zero + // This is possible since even if we receive coins in block 0, any transactions we'd make + // would resolve in block 1 (the first block we'll check under this non-zero rule) + let prior_block = b - 1; + if ScannerDb::::is_block_notable(&self.db, prior_block) && + (prior_block > highest_acknowledged) + { + break; + } + } + + iterated = true; + + todo!("TODO"); + + let mut txn = self.db.txn(); + // Update the next to check block + ScannerDb::::set_next_to_check_for_eventualities_block(&mut txn, next_to_check); + txn.commit(); + } + + // Run dependents if we successfully checked any blocks + Ok(iterated) + } +}