diff --git a/processor/scanner/src/eventuality/db.rs b/processor/scanner/src/eventuality/db.rs index f810ba2f..da8a3024 100644 --- a/processor/scanner/src/eventuality/db.rs +++ b/processor/scanner/src/eventuality/db.rs @@ -1,12 +1,17 @@ use core::marker::PhantomData; use scale::Encode; +use borsh::{BorshSerialize, BorshDeserialize}; use serai_db::{Get, DbTxn, create_db}; use primitives::{EncodableG, Eventuality, EventualityTracker}; use crate::{ScannerFeed, KeyFor, EventualityFor}; +// The DB macro doesn't support `BorshSerialize + BorshDeserialize` as a bound, hence this. +trait Borshy: BorshSerialize + BorshDeserialize {} +impl Borshy for T {} + create_db!( ScannerEventuality { // The next block to check for resolving eventualities @@ -15,6 +20,8 @@ create_db!( LatestHandledNotableBlock: () -> u64, SerializedEventualities: (key: K) -> Vec, + + RetiredKey: (block_number: u64) -> K, } ); @@ -51,7 +58,6 @@ impl EventualityDb { } SerializedEventualities::set(txn, EncodableG(key), &serialized); } - pub(crate) fn eventualities( getter: &impl Get, key: KeyFor, @@ -66,4 +72,19 @@ impl EventualityDb { } res } + + pub(crate) fn retire_key(txn: &mut impl DbTxn, block_number: u64, key: KeyFor) { + assert!( + RetiredKey::get::>>(txn, block_number).is_none(), + "retiring multiple keys within the same block" + ); + RetiredKey::set(txn, block_number, &EncodableG(key)); + } + pub(crate) fn take_retired_key(txn: &mut impl DbTxn, block_number: u64) -> Option> { + let res = RetiredKey::get::>>(txn, block_number).map(|res| res.0); + if res.is_some() { + RetiredKey::del::>>(txn, block_number); + } + res + } } diff --git a/processor/scanner/src/eventuality/mod.rs b/processor/scanner/src/eventuality/mod.rs index 7b5e3eed..c5f93789 100644 --- a/processor/scanner/src/eventuality/mod.rs +++ b/processor/scanner/src/eventuality/mod.rs @@ -248,6 +248,11 @@ impl> ContinuallyRan for EventualityTas let mut outputs = received_external_outputs; for key in &keys { + // If this is the key's activation block, activate it + if key.activation_block_number == b { + self.scheduler.activate_key(&mut txn, key.key); + } + let completed_eventualities = { let mut eventualities = EventualityDb::::eventualities(&txn, key.key); let completed_eventualities = block.check_for_eventuality_resolutions(&mut eventualities); @@ -349,11 +354,18 @@ impl> ContinuallyRan for EventualityTas // Retire this key `WINDOW_LENGTH` blocks in the future to ensure the scan task never // has a malleable view of the keys. - ScannerGlobalDb::::retire_key(&mut txn, b + S::WINDOW_LENGTH, key.key); + let retire_at = b + S::WINDOW_LENGTH; + ScannerGlobalDb::::retire_key(&mut txn, retire_at, key.key); + EventualityDb::::retire_key(&mut txn, retire_at, key.key); } } } + // If we retired any key at this block, retire it within the scheduler + if let Some(key) = EventualityDb::::take_retired_key(&mut txn, b) { + self.scheduler.retire_key(&mut txn, key); + } + // Update the next-to-check block EventualityDb::::set_next_to_check_for_eventualities_block(&mut txn, next_to_check); diff --git a/processor/scanner/src/lib.rs b/processor/scanner/src/lib.rs index 5f7e44a2..d90ca08e 100644 --- a/processor/scanner/src/lib.rs +++ b/processor/scanner/src/lib.rs @@ -137,6 +137,12 @@ pub trait ScannerFeed: 'static + Send + Sync + Clone { Ok(block) } + /// The dust threshold for the specified coin. + /// + /// This MUST be constant. Serai MUST NOT create internal outputs worth less than this. This + /// SHOULD be a value worth handling at a human level. + fn dust(&self, coin: Coin) -> Amount; + /// The cost to aggregate an input as of the specified block. /// /// This is defined as the transaction fee for a 2-input, 1-output transaction. @@ -145,12 +151,6 @@ pub trait ScannerFeed: 'static + Send + Sync + Clone { coin: Coin, reference_block: &Self::Block, ) -> Result; - - /// The dust threshold for the specified coin. - /// - /// This MUST be constant. Serai MUST NOT create internal outputs worth less than this. This - /// SHOULD be a value worth handling at a human level. - fn dust(&self, coin: Coin) -> Amount; } type KeyFor = <::Block as Block>::Key; @@ -187,6 +187,27 @@ pub struct SchedulerUpdate { /// The object responsible for accumulating outputs and planning new transactions. pub trait Scheduler: 'static + Send { + /// Activate a key. + /// + /// This SHOULD setup any necessary database structures. This SHOULD NOT cause the new key to + /// be used as the primary key. The multisig rotation time clearly establishes its steps. + fn activate_key(&mut self, txn: &mut impl DbTxn, key: KeyFor); + + /// Flush all outputs within a retiring key to the new key. + /// + /// When a key is activated, the existing multisig should retain its outputs and utility for a + /// certain time period. With `flush_key`, all outputs should be directed towards fulfilling some + /// obligation or the `new_key`. Every output MUST be connected to an Eventuality. If a key no + /// longer has active Eventualities, it MUST be able to be retired. + // TODO: Call this + fn flush_key(&mut self, txn: &mut impl DbTxn, retiring_key: KeyFor, new_key: KeyFor); + + /// Retire a key as it'll no longer be used. + /// + /// Any key retired MUST NOT still have outputs associated with it. This SHOULD be a NOP other + /// than any assertions and database cleanup. + fn retire_key(&mut self, txn: &mut impl DbTxn, key: KeyFor); + /// Accumulate outputs into the scheduler, yielding the Eventualities now to be scanned for. /// /// The `Vec` used as the key in the returned HashMap should be the encoded key the