mirror of
https://github.com/serai-dex/serai.git
synced 2025-01-27 21:15:57 +00:00
163 lines
5.8 KiB
Rust
163 lines
5.8 KiB
Rust
|
use core::marker::PhantomData;
|
||
|
|
||
|
use group::GroupEncoding;
|
||
|
|
||
|
use borsh::{BorshSerialize, BorshDeserialize};
|
||
|
use serai_db::{Get, DbTxn, create_db};
|
||
|
|
||
|
use primitives::{Id, Block, BorshG};
|
||
|
|
||
|
use crate::ScannerFeed;
|
||
|
|
||
|
// The DB macro doesn't support `BorshSerialize + BorshDeserialize` as a bound, hence this.
|
||
|
trait Borshy: BorshSerialize + BorshDeserialize {}
|
||
|
impl<T: BorshSerialize + BorshDeserialize> Borshy for T {}
|
||
|
|
||
|
#[derive(BorshSerialize, BorshDeserialize)]
|
||
|
struct SeraiKey<K: Borshy> {
|
||
|
activation_block_number: u64,
|
||
|
retirement_block_number: Option<u64>,
|
||
|
key: K,
|
||
|
}
|
||
|
|
||
|
create_db!(
|
||
|
Scanner {
|
||
|
BlockId: <I: Id>(number: u64) -> I,
|
||
|
BlockNumber: <I: Id>(id: I) -> u64,
|
||
|
|
||
|
ActiveKeys: <K: Borshy>() -> Vec<SeraiKey<K>>,
|
||
|
|
||
|
// 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,
|
||
|
|
||
|
// 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) -> (),
|
||
|
}
|
||
|
);
|
||
|
|
||
|
pub(crate) struct ScannerDb<S: ScannerFeed>(PhantomData<S>);
|
||
|
impl<S: ScannerFeed> ScannerDb<S> {
|
||
|
pub(crate) fn set_block(txn: &mut impl DbTxn, number: u64, id: <S::Block as Block>::Id) {
|
||
|
BlockId::set(txn, number, &id);
|
||
|
BlockNumber::set(txn, id, &number);
|
||
|
}
|
||
|
pub(crate) fn block_id(getter: &impl Get, number: u64) -> Option<<S::Block as Block>::Id> {
|
||
|
BlockId::get(getter, number)
|
||
|
}
|
||
|
pub(crate) fn block_number(getter: &impl Get, id: <S::Block as Block>::Id) -> Option<u64> {
|
||
|
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) {
|
||
|
let mut keys: Vec<SeraiKey<BorshG<S::Key>>> = 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<SeraiKey<BorshG<S::Key>>> =
|
||
|
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<Vec<SeraiKey<BorshG<S::Key>>>> {
|
||
|
ActiveKeys::get(getter)
|
||
|
}
|
||
|
|
||
|
pub(crate) fn set_start_block(
|
||
|
txn: &mut impl DbTxn,
|
||
|
start_block: u64,
|
||
|
id: <S::Block as Block>::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);
|
||
|
}
|
||
|
|
||
|
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<u64> {
|
||
|
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<u64> {
|
||
|
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<u64> {
|
||
|
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<u64> {
|
||
|
NextToCheckForEventualitiesBlock::get(getter)
|
||
|
}
|
||
|
}
|