mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-09 12:49:23 +00:00
Route burns through the scanner
This commit is contained in:
@@ -7,6 +7,7 @@ use serai_db::{Get, DbTxn, Db};
|
||||
|
||||
use serai_primitives::{NetworkId, Coin, Amount};
|
||||
use serai_in_instructions_primitives::Batch;
|
||||
use serai_coins_primitives::OutInstructionWithBalance;
|
||||
|
||||
use primitives::{task::*, Address, ReceivedOutput, Block};
|
||||
|
||||
@@ -15,7 +16,7 @@ mod lifetime;
|
||||
|
||||
// Database schema definition and associated functions.
|
||||
mod db;
|
||||
use db::ScannerGlobalDb;
|
||||
use db::{ScannerGlobalDb, SubstrateToEventualityDb};
|
||||
// Task to index the blockchain, ensuring we don't reorganize finalized blocks.
|
||||
mod index;
|
||||
// Scans blocks for received coins.
|
||||
@@ -147,7 +148,7 @@ pub trait ScannerFeed: 'static + Send + Sync + Clone {
|
||||
|
||||
/// The dust threshold for the specified coin.
|
||||
///
|
||||
/// This MUST be constant. Serai MJUST NOT create internal outputs worth less than this. This
|
||||
/// 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;
|
||||
}
|
||||
@@ -195,6 +196,40 @@ pub trait Scheduler<S: ScannerFeed>: 'static + Send {
|
||||
txn: &mut impl DbTxn,
|
||||
update: SchedulerUpdate<S>,
|
||||
) -> HashMap<Vec<u8>, Vec<EventualityFor<S>>>;
|
||||
|
||||
/// Fulfill a series of payments, yielding the Eventualities now to be scanned for.
|
||||
///
|
||||
/// Any Eventualities returned by this function must include an output-to-self (such as a Branch
|
||||
/// or Change), unless they descend from a transaction returned by this function which satisfies
|
||||
/// that requirement.
|
||||
///
|
||||
/// The `Vec<u8>` used as the key in the returned HashMap should be the encoded key the
|
||||
/// Eventualities are for.
|
||||
/*
|
||||
We need an output-to-self so we can detect a block with an Eventuality completion with regards
|
||||
to Burns, forcing us to ensure we have accumulated all the Burns we should by the time we
|
||||
handle that block. We explicitly don't require children have this requirement as by detecting
|
||||
the first resolution, we ensure we'll accumulate the Burns (therefore becoming aware of the
|
||||
childrens' Eventualities, enabling recognizing their resolutions).
|
||||
|
||||
This carve out enables the following:
|
||||
|
||||
------------------ Fulfillment TX ----------------------
|
||||
| Primary Output | ---------------> | New Primary Output |
|
||||
------------------ | ----------------------
|
||||
|
|
||||
| ------------------------------
|
||||
|------> | Branching Output for Burns |
|
||||
------------------------------
|
||||
|
||||
Without wasting pointless Change outputs on every transaction (as there's a single parent which
|
||||
has an output-to-self).
|
||||
*/
|
||||
fn fulfill(
|
||||
&mut self,
|
||||
txn: &mut impl DbTxn,
|
||||
payments: Vec<OutInstructionWithBalance>,
|
||||
) -> HashMap<Vec<u8>, Vec<EventualityFor<S>>>;
|
||||
}
|
||||
|
||||
/// A representation of a scanner.
|
||||
@@ -242,6 +277,8 @@ impl<S: ScannerFeed> Scanner<S> {
|
||||
///
|
||||
/// This means this block was ordered on Serai in relation to `Burn` events, and all validators
|
||||
/// have achieved synchrony on it.
|
||||
///
|
||||
/// The calls to this function must be ordered with regards to `queue_burns`.
|
||||
pub fn acknowledge_block(
|
||||
&mut self,
|
||||
mut txn: impl DbTxn,
|
||||
@@ -249,10 +286,23 @@ impl<S: ScannerFeed> Scanner<S> {
|
||||
key_to_activate: Option<KeyFor<S>>,
|
||||
) {
|
||||
log::info!("acknowledging block {block_number}");
|
||||
|
||||
assert!(
|
||||
ScannerGlobalDb::<S>::is_block_notable(&txn, block_number),
|
||||
"acknowledging a block which wasn't notable"
|
||||
);
|
||||
if let Some(prior_highest_acknowledged_block) =
|
||||
ScannerGlobalDb::<S>::highest_acknowledged_block(&txn)
|
||||
{
|
||||
assert!(block_number > prior_highest_acknowledged_block, "acknowledging blocks out-of-order");
|
||||
for b in (prior_highest_acknowledged_block + 1) .. (block_number - 1) {
|
||||
assert!(
|
||||
!ScannerGlobalDb::<S>::is_block_notable(&txn, b),
|
||||
"skipped acknowledging a block which was notable"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ScannerGlobalDb::<S>::set_highest_acknowledged_block(&mut txn, block_number);
|
||||
if let Some(key_to_activate) = key_to_activate {
|
||||
ScannerGlobalDb::<S>::queue_key(&mut txn, block_number + S::WINDOW_LENGTH, key_to_activate);
|
||||
@@ -268,13 +318,38 @@ impl<S: ScannerFeed> Scanner<S> {
|
||||
|
||||
/// Queue Burns.
|
||||
///
|
||||
/// The scanner only updates the scheduler with new outputs upon acknowledging a block. We can
|
||||
/// safely queue Burns so long as they're only actually added once we've handled the outputs from
|
||||
/// the block acknowledged prior to their queueing.
|
||||
pub fn queue_burns(&mut self, txn: &mut impl DbTxn, burns: Vec<()>) {
|
||||
/// The scanner only updates the scheduler with new outputs upon acknowledging a block. The
|
||||
/// ability to fulfill Burns, and therefore their order, is dependent on the current output
|
||||
/// state. This immediately sets a bound that this function is ordered with regards to
|
||||
/// `acknowledge_block`.
|
||||
/*
|
||||
The fact Burns can be queued during any Substrate block is problematic. The scanner is allowed
|
||||
to scan anything within the window set by the Eventuality task. The Eventuality task is allowed
|
||||
to handle all blocks until it reaches a block needing acknowledgement.
|
||||
|
||||
This means we may queue Burns when the latest acknowledged block is 1, yet we've already
|
||||
scanned 101. Such Burns may complete back in block 2, and we simply wouldn't have noticed due
|
||||
to not having yet generated the Eventualities.
|
||||
|
||||
We solve this by mandating all transactions made as the result of an Eventuality include a
|
||||
output-to-self worth at least `N::DUST`. If that occurs, the scanner will force a consensus
|
||||
protocol on block 2. Accordingly, we won't scan all the way to block 101 (missing the
|
||||
resolution of the Eventuality) as we'll obtain synchrony on block 2 and all Burns queued prior
|
||||
to it.
|
||||
|
||||
Another option would be to re-check historical blocks, yet this would potentially redo an
|
||||
unbounded amount of work. It would also not allow us to safely detect if received outputs were
|
||||
in fact the result of Eventualities or not.
|
||||
|
||||
Another option would be to schedule Burns after the next-acknowledged block, yet this would add
|
||||
latency and likely practically require we add regularly scheduled notable blocks (which may be
|
||||
unnecessary).
|
||||
*/
|
||||
pub fn queue_burns(&mut self, txn: &mut impl DbTxn, burns: &Vec<OutInstructionWithBalance>) {
|
||||
let queue_as_of = ScannerGlobalDb::<S>::highest_acknowledged_block(txn)
|
||||
.expect("queueing Burns yet never acknowledged a block");
|
||||
todo!("TODO")
|
||||
|
||||
SubstrateToEventualityDb::send_burns(txn, queue_as_of, burns)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user