use core::marker::PhantomData; use borsh::{BorshSerialize, BorshDeserialize}; use serai_db::{Get, DbTxn, create_db}; use primitives::{Id, ReceivedOutput, Block, BorshG}; use crate::ScannerFeed; // The DB macro doesn't support `BorshSerialize + BorshDeserialize` as a bound, hence this. trait Borshy: BorshSerialize + BorshDeserialize {} impl Borshy for T {} #[derive(BorshSerialize, BorshDeserialize)] pub(crate) struct SeraiKey { pub(crate) activation_block_number: u64, pub(crate) retirement_block_number: Option, pub(crate) key: K, } create_db!( Scanner { BlockId: (number: u64) -> I, BlockNumber: (id: I) -> u64, ActiveKeys: () -> Vec>, // The latest finalized block to appear of a blockchain LatestFinalizedBlock: () -> u64, // The latest block which it's safe to scan (dependent on what Serai has acknowledged scanning) LatestScannableBlock: () -> u64, // The next block to scan for received outputs NextToScanForOutputsBlock: () -> u64, // The next block to check for resolving eventualities NextToCheckForEventualitiesBlock: () -> u64, // The next block to potentially report NextToPotentiallyReportBlock: () -> u64, // The highest acknowledged block HighestAcknowledgedBlock: () -> u64, // If a block was notable /* A block is notable if one of three conditions are met: 1) We activated a key within this block. 2) We retired a key within this block. 3) We received outputs within this block. The first two conditions, and the reasoning for them, is extensively documented in `spec/processor/Multisig Rotation.md`. The third is obvious (as any block we receive outputs in needs synchrony so that we can spend the received outputs). We save if a block is notable here by either the scan for received outputs task or the check for eventuality completion task. Once a block has been processed by both, the reporting task will report any notable blocks. Finally, the task which sets the block safe to scan to makes its decision based on the notable blocks and the acknowledged blocks. */ // This collapses from `bool` to `()`, using if the value was set for true and false otherwise NotableBlock: (number: u64) -> (), SerializedOutputs: (block_number: u64) -> Vec, } ); pub(crate) struct ScannerDb(PhantomData); impl ScannerDb { pub(crate) fn set_block(txn: &mut impl DbTxn, number: u64, id: ::Id) { BlockId::set(txn, number, &id); BlockNumber::set(txn, id, &number); } pub(crate) fn block_id(getter: &impl Get, number: u64) -> Option<::Id> { BlockId::get(getter, number) } pub(crate) fn block_number(getter: &impl Get, id: ::Id) -> Option { BlockNumber::get(getter, id) } // activation_block_number is inclusive, so the key will be scanned for starting at the specified // block pub(crate) fn queue_key(txn: &mut impl DbTxn, activation_block_number: u64, key: S::Key) { // Set this block as notable NotableBlock::set(txn, activation_block_number, &()); // Push the key let mut keys: Vec>> = ActiveKeys::get(txn).unwrap_or(vec![]); for key_i in &keys { if key == key_i.key.0 { panic!("queueing a key prior queued"); } } keys.push(SeraiKey { activation_block_number, retirement_block_number: None, key: BorshG(key), }); ActiveKeys::set(txn, &keys); } // retirement_block_number is inclusive, so the key will no longer be scanned for as of the // specified block pub(crate) fn retire_key(txn: &mut impl DbTxn, retirement_block_number: u64, key: S::Key) { let mut keys: Vec>> = ActiveKeys::get(txn).expect("retiring key yet no active keys"); assert!(keys.len() > 1, "retiring our only key"); for i in 0 .. keys.len() { if key == keys[i].key.0 { keys[i].retirement_block_number = Some(retirement_block_number); ActiveKeys::set(txn, &keys); return; } // This is not the key in question, but since it's older, it already should've been queued // for retirement assert!( keys[i].retirement_block_number.is_some(), "older key wasn't retired before newer key" ); } panic!("retiring key yet not present in keys") } pub(crate) fn keys(getter: &impl Get) -> Option>>> { ActiveKeys::get(getter) } pub(crate) fn set_start_block( txn: &mut impl DbTxn, start_block: u64, id: ::Id, ) { Self::set_block(txn, start_block, id); LatestFinalizedBlock::set(txn, &start_block); LatestScannableBlock::set(txn, &start_block); NextToScanForOutputsBlock::set(txn, &start_block); NextToCheckForEventualitiesBlock::set(txn, &start_block); NextToPotentiallyReportBlock::set(txn, &start_block); } pub(crate) fn set_latest_finalized_block(txn: &mut impl DbTxn, latest_finalized_block: u64) { LatestFinalizedBlock::set(txn, &latest_finalized_block); } pub(crate) fn latest_finalized_block(getter: &impl Get) -> Option { LatestFinalizedBlock::get(getter) } pub(crate) fn set_latest_scannable_block(txn: &mut impl DbTxn, latest_scannable_block: u64) { LatestScannableBlock::set(txn, &latest_scannable_block); } pub(crate) fn latest_scannable_block(getter: &impl Get) -> Option { LatestScannableBlock::get(getter) } pub(crate) fn set_next_to_scan_for_outputs_block( txn: &mut impl DbTxn, next_to_scan_for_outputs_block: u64, ) { NextToScanForOutputsBlock::set(txn, &next_to_scan_for_outputs_block); } pub(crate) fn next_to_scan_for_outputs_block(getter: &impl Get) -> Option { NextToScanForOutputsBlock::get(getter) } pub(crate) fn set_next_to_check_for_eventualities_block( txn: &mut impl DbTxn, next_to_check_for_eventualities_block: u64, ) { NextToCheckForEventualitiesBlock::set(txn, &next_to_check_for_eventualities_block); } pub(crate) fn next_to_check_for_eventualities_block(getter: &impl Get) -> Option { NextToCheckForEventualitiesBlock::get(getter) } pub(crate) fn set_next_to_potentially_report_block( txn: &mut impl DbTxn, next_to_potentially_report_block: u64, ) { NextToPotentiallyReportBlock::set(txn, &next_to_potentially_report_block); } pub(crate) fn next_to_potentially_report_block(getter: &impl Get) -> Option { 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; } // Set this block as notable NotableBlock::set(txn, block_number, &()); let mut buf = Vec::with_capacity(outputs.len() * 128); for output in outputs { output.write(&mut buf).unwrap(); } SerializedOutputs::set(txn, block_number, &buf); } pub(crate) fn is_block_notable(getter: &impl Get, number: u64) -> bool { NotableBlock::get(getter, number).is_some() } }