diff --git a/processor/primitives/src/lib.rs b/processor/primitives/src/lib.rs index 535dd14f..45f02571 100644 --- a/processor/primitives/src/lib.rs +++ b/processor/primitives/src/lib.rs @@ -5,7 +5,7 @@ use core::fmt::Debug; use std::io; -use group::GroupEncoding; +use group::{Group, GroupEncoding}; use serai_primitives::Balance; @@ -137,9 +137,8 @@ pub trait ReceivedOutput: fn read(reader: &mut R) -> io::Result; } -/// A block from an external network. -#[async_trait::async_trait] -pub trait Block: Send + Sync + Sized + Clone + Debug { +/// A block header from an external network. +pub trait BlockHeader: Send + Sync + Sized + Clone + Debug { /// The type used to identify blocks. type Id: 'static + Id; /// The ID of this block. @@ -148,6 +147,31 @@ pub trait Block: Send + Sync + Sized + Clone + Debug { fn parent(&self) -> Self::Id; } +/// A block from an external network. +/// +/// A block is defined as a consensus event associated with a set of transactions. It is not +/// necessary to literally define it as whatever the external network defines as a block. For +/// external networks which finalize block(s), this block type should be a representation of all +/// transactions within a period finalization (whether block or epoch). +#[async_trait::async_trait] +pub trait Block: Send + Sync + Sized + Clone + Debug { + /// The type used for this block's header. + type Header: BlockHeader; + + /// The type used to represent keys on this external network. + type Key: Group + GroupEncoding; + /// The type used to represent addresses on this external network. + type Address; + /// The type used to represent received outputs on this external network. + type Output: ReceivedOutput; + + /// The ID of this block. + fn id(&self) -> ::Id; + + /// Scan all outputs within this block to find the outputs spendable by this key. + fn scan_for_outputs(&self, key: Self::Key) -> Vec; +} + /// A wrapper for a group element which implements the borsh traits. #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct BorshG(pub G); diff --git a/processor/scanner/src/db.rs b/processor/scanner/src/db.rs index c7cbd253..0edfad97 100644 --- a/processor/scanner/src/db.rs +++ b/processor/scanner/src/db.rs @@ -5,7 +5,7 @@ use serai_db::{Get, DbTxn, create_db}; use primitives::{Id, ReceivedOutput, Block, BorshG}; -use crate::ScannerFeed; +use crate::{ScannerFeed, BlockIdFor, KeyFor, OutputFor}; // The DB macro doesn't support `BorshSerialize + BorshDeserialize` as a bound, hence this. trait Borshy: BorshSerialize + BorshDeserialize {} @@ -64,25 +64,25 @@ create_db!( pub(crate) struct ScannerDb(PhantomData); impl ScannerDb { - pub(crate) fn set_block(txn: &mut impl DbTxn, number: u64, id: ::Id) { + pub(crate) fn set_block(txn: &mut impl DbTxn, number: u64, id: BlockIdFor) { BlockId::set(txn, number, &id); BlockNumber::set(txn, id, &number); } - pub(crate) fn block_id(getter: &impl Get, number: u64) -> Option<::Id> { + pub(crate) fn block_id(getter: &impl Get, number: u64) -> Option> { BlockId::get(getter, number) } - pub(crate) fn block_number(getter: &impl Get, id: ::Id) -> Option { + pub(crate) fn block_number(getter: &impl Get, id: BlockIdFor) -> 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) { + pub(crate) fn queue_key(txn: &mut impl DbTxn, activation_block_number: u64, key: KeyFor) { // Set this block as notable NotableBlock::set(txn, activation_block_number, &()); // Push the key - let mut keys: Vec>> = ActiveKeys::get(txn).unwrap_or(vec![]); + 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"); @@ -97,8 +97,8 @@ impl ScannerDb { } // 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>> = + pub(crate) fn retire_key(txn: &mut impl DbTxn, retirement_block_number: u64, key: KeyFor) { + let mut keys: Vec>>> = ActiveKeys::get(txn).expect("retiring key yet no active keys"); assert!(keys.len() > 1, "retiring our only key"); @@ -118,15 +118,11 @@ impl ScannerDb { } panic!("retiring key yet not present in keys") } - pub(crate) fn keys(getter: &impl Get) -> Option>>> { + 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, - ) { + pub(crate) fn set_start_block(txn: &mut impl DbTxn, start_block: u64, id: BlockIdFor) { Self::set_block(txn, start_block, id); LatestFinalizedBlock::set(txn, &start_block); LatestScannableBlock::set(txn, &start_block); @@ -189,11 +185,7 @@ impl ScannerDb { HighestAcknowledgedBlock::get(getter) } - pub(crate) fn set_outputs( - txn: &mut impl DbTxn, - block_number: u64, - outputs: Vec>, - ) { + 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/index.rs b/processor/scanner/src/index.rs index 7967d5df..de68522e 100644 --- a/processor/scanner/src/index.rs +++ b/processor/scanner/src/index.rs @@ -1,6 +1,6 @@ use serai_db::{Db, DbTxn}; -use primitives::{Id, Block}; +use primitives::{Id, BlockHeader}; // TODO: Localize to IndexDb? use crate::{db::ScannerDb, ScannerFeed, ContinuallyRan}; @@ -43,7 +43,7 @@ impl ContinuallyRan for IndexFinalizedTask { // Index the hashes of all blocks until the latest finalized block for b in (our_latest_finalized + 1) ..= latest_finalized { - let block = match self.feed.block_by_number(b).await { + let block = match self.feed.block_header_by_number(b).await { Ok(block) => block, Err(e) => Err(format!("couldn't fetch block {b}: {e:?}"))?, }; diff --git a/processor/scanner/src/lib.rs b/processor/scanner/src/lib.rs index a6f3e899..addebb60 100644 --- a/processor/scanner/src/lib.rs +++ b/processor/scanner/src/lib.rs @@ -2,7 +2,7 @@ use core::{fmt::Debug, time::Duration}; use tokio::sync::mpsc; -use primitives::{ReceivedOutput, Block}; +use primitives::{ReceivedOutput, BlockHeader, Block}; mod db; mod index; @@ -21,15 +21,6 @@ pub trait ScannerFeed: Send + Sync { /// This value must be at least `1`. const CONFIRMATIONS: u64; - /// The type of the key used to receive coins on this blockchain. - type Key: group::Group + group::GroupEncoding; - - /// The type of the address used to specify who to send coins to on this blockchain. - type Address; - - /// The type representing a received (and spendable) output. - type Output: ReceivedOutput; - /// The representation of a block for this blockchain. /// /// A block is defined as a consensus event associated with a set of transactions. It is not @@ -58,17 +49,20 @@ pub trait ScannerFeed: Send + Sync { Ok(self.latest_block_number().await? - Self::CONFIRMATIONS) } + /// Fetch a block header by its number. + async fn block_header_by_number( + &self, + number: u64, + ) -> Result<::Header, Self::EphemeralError>; + /// Fetch a block by its number. async fn block_by_number(&self, number: u64) -> Result; - - /// Scan a block for its outputs. - async fn scan_for_outputs( - &self, - block: &Self::Block, - key: Self::Key, - ) -> Result, Self::EphemeralError>; } +type BlockIdFor = <<::Block as Block>::Header as BlockHeader>::Id; +type KeyFor = <::Block as Block>::Key; +type OutputFor = <::Block as Block>::Output; + /// A handle to immediately run an iteration of a task. #[derive(Clone)] pub(crate) struct RunNowHandle(mpsc::Sender<()>); diff --git a/processor/scanner/src/scan.rs b/processor/scanner/src/scan.rs index 92165002..6743d950 100644 --- a/processor/scanner/src/scan.rs +++ b/processor/scanner/src/scan.rs @@ -60,12 +60,7 @@ impl ContinuallyRan for ScanForOutputsTask { continue; } - for output in self - .feed - .scan_for_outputs(&block, key.key.0) - .await - .map_err(|e| format!("failed to scan block {b}: {e:?}"))? - { + for output in block.scan_for_outputs(key.key.0) { assert_eq!(output.key(), key.key.0); // TODO: Check for dust outputs.push(output);