From 4f6d91037ef6781801349238e30e8382e254a83a Mon Sep 17 00:00:00 2001
From: Luke Parker <lukeparker5132@gmail.com>
Date: Thu, 29 Aug 2024 16:27:00 -0400
Subject: [PATCH] Call flush_key

---
 processor/scanner/src/db.rs              | 36 +++++++++++++++++---
 processor/scanner/src/eventuality/mod.rs |  9 ++++-
 processor/scanner/src/lifetime.rs        | 43 +++++++++++++++---------
 spec/processor/Multisig Rotation.md      |  3 +-
 4 files changed, 68 insertions(+), 23 deletions(-)

diff --git a/processor/scanner/src/db.rs b/processor/scanner/src/db.rs
index a6272eeb..20aa2999 100644
--- a/processor/scanner/src/db.rs
+++ b/processor/scanner/src/db.rs
@@ -11,7 +11,8 @@ use serai_coins_primitives::OutInstructionWithBalance;
 use primitives::{EncodableG, Address, ReceivedOutput};
 
 use crate::{
-  lifetime::LifetimeStage, ScannerFeed, KeyFor, AddressFor, OutputFor, Return,
+  lifetime::{LifetimeStage, Lifetime},
+  ScannerFeed, KeyFor, AddressFor, OutputFor, Return,
   scan::next_to_scan_for_outputs_block,
 };
 
@@ -30,6 +31,7 @@ pub(crate) struct SeraiKey<K> {
   pub(crate) stage: LifetimeStage,
   pub(crate) activation_block_number: u64,
   pub(crate) block_at_which_reporting_starts: u64,
+  pub(crate) block_at_which_forwarding_starts: Option<u64>,
 }
 
 pub(crate) struct OutputWithInInstruction<S: ScannerFeed> {
@@ -82,7 +84,7 @@ create_db!(
     /*
       A block is notable if one of three conditions are met:
 
-      1) We activated a key within this block.
+      1) We activated a key within this block (or explicitly forward to an activated key).
       2) We retired a key within this block.
       3) We received outputs within this block.
 
@@ -120,9 +122,32 @@ impl<S: ScannerFeed> ScannerGlobalDb<S> {
 
     // TODO: Panic if we've ever seen this key before
 
-    // Push the key
+    // Fetch the existing keys
     let mut keys: Vec<SeraiKeyDbEntry<EncodableG<KeyFor<S>>>> =
       ActiveKeys::get(txn).unwrap_or(vec![]);
+
+    // If this new key retires a key, mark the block at which forwarding explicitly occurs notable
+    // This lets us obtain synchrony over the transactions we'll make to accomplish this
+    if let Some(key_retired_by_this) = keys.last() {
+      NotableBlock::set(
+        txn,
+        Lifetime::calculate::<S>(
+          // The 'current block number' used for this calculation
+          activation_block_number,
+          // The activation block of the key we're getting the lifetime of
+          key_retired_by_this.activation_block_number,
+          // The activation block of the key which will retire this key
+          Some(activation_block_number),
+        )
+        .block_at_which_forwarding_starts
+        .expect(
+          "didn't calculate the block forwarding starts at despite passing the next key's info",
+        ),
+        &(),
+      );
+    }
+
+    // Push and save the next key
     keys.push(SeraiKeyDbEntry { activation_block_number, key: EncodableG(key) });
     ActiveKeys::set(txn, &keys);
   }
@@ -185,8 +210,8 @@ impl<S: ScannerFeed> ScannerGlobalDb<S> {
       if block_number < raw_keys[i].activation_block_number {
         continue;
       }
-      let (stage, block_at_which_reporting_starts) =
-        LifetimeStage::calculate_stage_and_reporting_start_block::<S>(
+      let Lifetime { stage, block_at_which_reporting_starts, block_at_which_forwarding_starts } =
+        Lifetime::calculate::<S>(
           block_number,
           raw_keys[i].activation_block_number,
           raw_keys.get(i + 1).map(|key| key.activation_block_number),
@@ -196,6 +221,7 @@ impl<S: ScannerFeed> ScannerGlobalDb<S> {
         stage,
         activation_block_number: raw_keys[i].activation_block_number,
         block_at_which_reporting_starts,
+        block_at_which_forwarding_starts,
       });
     }
     assert!(keys.len() <= 2, "more than two keys active");
diff --git a/processor/scanner/src/eventuality/mod.rs b/processor/scanner/src/eventuality/mod.rs
index c5f93789..002131cc 100644
--- a/processor/scanner/src/eventuality/mod.rs
+++ b/processor/scanner/src/eventuality/mod.rs
@@ -341,8 +341,15 @@ impl<D: Db, S: ScannerFeed, Sch: Scheduler<S>> ContinuallyRan for EventualityTas
         intake_eventualities::<S>(&mut txn, new_eventualities);
       }
 
-      // Now that we've intaked any Eventualities caused, check if we're retiring any keys
       for key in &keys {
+        // If this is the block at which forwarding starts for this key, flush it
+        // We do this after we issue the above update for any efficiencies gained by doing so
+        if key.block_at_which_forwarding_starts == Some(b) {
+          assert!(key.key != keys.last().unwrap().key);
+          self.scheduler.flush_key(&mut txn, key.key, keys.last().unwrap().key);
+        }
+
+        // Now that we've intaked any Eventualities caused, check if we're retiring any keys
         if key.stage == LifetimeStage::Finishing {
           let eventualities = EventualityDb::<S>::eventualities(&txn, key.key);
           // TODO: This assumes the Scheduler is empty
diff --git a/processor/scanner/src/lifetime.rs b/processor/scanner/src/lifetime.rs
index 09df7a37..e15c0f55 100644
--- a/processor/scanner/src/lifetime.rs
+++ b/processor/scanner/src/lifetime.rs
@@ -35,17 +35,25 @@ pub(crate) enum LifetimeStage {
   Finishing,
 }
 
-impl LifetimeStage {
-  /// Get the stage of its lifetime this multisig is in, and the block at which we start reporting
-  /// outputs to it.
+/// The lifetime of the multisig, including various block numbers.
+pub(crate) struct Lifetime {
+  pub(crate) stage: LifetimeStage,
+  pub(crate) block_at_which_reporting_starts: u64,
+  // This is only Some if the next key's activation block number is passed to calculate, and the
+  // stage is at least `LifetimeStage::Active.`
+  pub(crate) block_at_which_forwarding_starts: Option<u64>,
+}
+
+impl Lifetime {
+  /// Get the lifetime of this multisig.
   ///
   /// Panics if the multisig being calculated for isn't actually active and a variety of other
   /// insane cases.
-  pub(crate) fn calculate_stage_and_reporting_start_block<S: ScannerFeed>(
+  pub(crate) fn calculate<S: ScannerFeed>(
     block_number: u64,
     activation_block_number: u64,
     next_keys_activation_block_number: Option<u64>,
-  ) -> (Self, u64) {
+  ) -> Self {
     assert!(
       activation_block_number >= block_number,
       "calculating lifetime stage for an inactive multisig"
@@ -55,14 +63,14 @@ impl LifetimeStage {
     let active_yet_not_reporting_end_block =
       activation_block_number + S::CONFIRMATIONS + S::TEN_MINUTES;
     // The exclusive end block is the inclusive start block
-    let reporting_start_block = active_yet_not_reporting_end_block;
+    let block_at_which_reporting_starts = active_yet_not_reporting_end_block;
     if block_number < active_yet_not_reporting_end_block {
-      return (LifetimeStage::ActiveYetNotReporting, reporting_start_block);
+      return Lifetime { stage: LifetimeStage::ActiveYetNotReporting, block_at_which_reporting_starts, block_at_which_forwarding_starts: None };
     }
 
     let Some(next_keys_activation_block_number) = next_keys_activation_block_number else {
       // If there is no next multisig, this is the active multisig
-      return (LifetimeStage::Active, reporting_start_block);
+      return Lifetime { stage: LifetimeStage::Active, block_at_which_reporting_starts, block_at_which_forwarding_starts: None };
     };
 
     assert!(
@@ -70,19 +78,22 @@ impl LifetimeStage {
       "next set of keys activated before this multisig activated"
     );
 
-    // If the new multisig is still having its activation block finalized on-chain, this multisig
-    // is still active (step 3)
     let new_active_yet_not_reporting_end_block =
       next_keys_activation_block_number + S::CONFIRMATIONS + S::TEN_MINUTES;
+    let new_active_and_used_for_change_end_block =
+      new_active_yet_not_reporting_end_block + S::CONFIRMATIONS;
+    // The exclusive end block is the inclusive start block
+    let block_at_which_forwarding_starts = Some(new_active_and_used_for_change_end_block);
+
+    // If the new multisig is still having its activation block finalized on-chain, this multisig
+    // is still active (step 3)
     if block_number < new_active_yet_not_reporting_end_block {
-      return (LifetimeStage::Active, reporting_start_block);
+      return Lifetime { stage: LifetimeStage::Active, block_at_which_reporting_starts, block_at_which_forwarding_starts };
     }
 
     // Step 4 details a further CONFIRMATIONS
-    let new_active_and_used_for_change_end_block =
-      new_active_yet_not_reporting_end_block + S::CONFIRMATIONS;
     if block_number < new_active_and_used_for_change_end_block {
-      return (LifetimeStage::UsingNewForChange, reporting_start_block);
+      return Lifetime { stage: LifetimeStage::UsingNewForChange, block_at_which_reporting_starts, block_at_which_forwarding_starts };
     }
 
     // Step 5 details a further 6 hours
@@ -90,10 +101,10 @@ impl LifetimeStage {
     let new_active_and_forwarded_to_end_block =
       new_active_and_used_for_change_end_block + (6 * 6 * S::TEN_MINUTES);
     if block_number < new_active_and_forwarded_to_end_block {
-      return (LifetimeStage::Forwarding, reporting_start_block);
+      return Lifetime { stage: LifetimeStage::Forwarding, block_at_which_reporting_starts, block_at_which_forwarding_starts };
     }
 
     // Step 6
-    (LifetimeStage::Finishing, reporting_start_block)
+    Lifetime { stage: LifetimeStage::Finishing, block_at_which_reporting_starts, block_at_which_forwarding_starts }
   }
 }
diff --git a/spec/processor/Multisig Rotation.md b/spec/processor/Multisig Rotation.md
index ff5c3d28..916ce56b 100644
--- a/spec/processor/Multisig Rotation.md	
+++ b/spec/processor/Multisig Rotation.md	
@@ -102,7 +102,8 @@ The following timeline is established:
 
 5) For the next 6 hours, all non-`Branch` outputs received are immediately
    forwarded to the new multisig. Only external transactions to the new multisig
-   are included in `Batch`s.
+   are included in `Batch`s. Any outputs not yet transferred as change are
+   explicitly transferred.
 
    The new multisig infers the `InInstruction`, and refund address, for
    forwarded `External` outputs via reading what they were for the original