mirror of
https://github.com/serai-dex/serai.git
synced 2024-12-24 12:39:33 +00:00
Add Eventuality back to processor primitives
Also splits crate into modules.
This commit is contained in:
parent
4e29678799
commit
f2ee4daf43
4 changed files with 194 additions and 142 deletions
40
processor/primitives/src/block.rs
Normal file
40
processor/primitives/src/block.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
use core::fmt::Debug;
|
||||||
|
|
||||||
|
use group::{Group, GroupEncoding};
|
||||||
|
|
||||||
|
use crate::{Id, ReceivedOutput};
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
fn id(&self) -> Self::Id;
|
||||||
|
/// The ID of the parent block.
|
||||||
|
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<Self::Key, Self::Address>;
|
||||||
|
|
||||||
|
/// The ID of this block.
|
||||||
|
fn id(&self) -> <Self::Header as BlockHeader>::Id;
|
||||||
|
|
||||||
|
/// Scan all outputs within this block to find the outputs spendable by this key.
|
||||||
|
fn scan_for_outputs(&self, key: Self::Key) -> Vec<Self::Output>;
|
||||||
|
}
|
31
processor/primitives/src/eventuality.rs
Normal file
31
processor/primitives/src/eventuality.rs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
/// A description of a transaction which will eventually happen.
|
||||||
|
pub trait Eventuality: Sized + Send + Sync {
|
||||||
|
/// A unique byte sequence which can be used to identify potentially resolving transactions.
|
||||||
|
///
|
||||||
|
/// Both a transaction and an Eventuality are expected to be able to yield lookup sequences.
|
||||||
|
/// Lookup sequences MUST be unique to the Eventuality and identical to any transaction's which
|
||||||
|
/// satisfies this Eventuality. Transactions which don't satisfy this Eventuality MAY also have
|
||||||
|
/// an identical lookup sequence.
|
||||||
|
///
|
||||||
|
/// This is used to find the Eventuality a transaction MAY resolve so we don't have to check all
|
||||||
|
/// transactions against all Eventualities. Once the potential resolved Eventuality is
|
||||||
|
/// identified, the full check is performed.
|
||||||
|
fn lookup(&self) -> Vec<u8>;
|
||||||
|
|
||||||
|
/// Read an Eventuality.
|
||||||
|
fn read<R: io::Read>(reader: &mut R) -> io::Result<Self>;
|
||||||
|
/// Serialize an Eventuality to a `Vec<u8>`.
|
||||||
|
fn serialize(&self) -> Vec<u8>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A tracker of unresolved Eventualities.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct EventualityTracker<E: Eventuality> {
|
||||||
|
/// The active Eventualities.
|
||||||
|
///
|
||||||
|
/// These are keyed by their lookups.
|
||||||
|
pub active_eventualities: HashMap<Vec<u8>, E>,
|
||||||
|
}
|
|
@ -3,15 +3,21 @@
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
use std::io;
|
|
||||||
|
|
||||||
use group::{Group, GroupEncoding};
|
use group::GroupEncoding;
|
||||||
|
|
||||||
use serai_primitives::Balance;
|
|
||||||
|
|
||||||
use scale::{Encode, Decode};
|
use scale::{Encode, Decode};
|
||||||
use borsh::{BorshSerialize, BorshDeserialize};
|
use borsh::{BorshSerialize, BorshDeserialize};
|
||||||
|
|
||||||
|
mod output;
|
||||||
|
pub use output::*;
|
||||||
|
|
||||||
|
mod eventuality;
|
||||||
|
pub use eventuality::*;
|
||||||
|
|
||||||
|
mod block;
|
||||||
|
pub use block::*;
|
||||||
|
|
||||||
/// An ID for an output/transaction/block/etc.
|
/// An ID for an output/transaction/block/etc.
|
||||||
///
|
///
|
||||||
/// IDs don't need to implement `Copy`, enabling `[u8; 33]`, `[u8; 64]` to be used. IDs are still
|
/// IDs don't need to implement `Copy`, enabling `[u8; 33]`, `[u8; 64]` to be used. IDs are still
|
||||||
|
@ -51,141 +57,3 @@ impl<G: GroupEncoding> BorshDeserialize for BorshG<G> {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The type of the output.
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
|
||||||
pub enum OutputType {
|
|
||||||
/// An output received to the address external payments use.
|
|
||||||
///
|
|
||||||
/// This is reported to Substrate in a `Batch`.
|
|
||||||
External,
|
|
||||||
|
|
||||||
/// A branch output.
|
|
||||||
///
|
|
||||||
/// Given a known output set, and a known series of outbound transactions, we should be able to
|
|
||||||
/// form a completely deterministic schedule S. The issue is when S has TXs which spend prior TXs
|
|
||||||
/// in S (which is needed for our logarithmic scheduling). In order to have the descendant TX,
|
|
||||||
/// say S[1], build off S[0], we need to observe when S[0] is included on-chain.
|
|
||||||
///
|
|
||||||
/// We cannot.
|
|
||||||
///
|
|
||||||
/// Monero (and other privacy coins) do not expose their UTXO graphs. Even if we know how to
|
|
||||||
/// create S[0], and the actual payment info behind it, we cannot observe it on the blockchain
|
|
||||||
/// unless we participated in creating it. Locking the entire schedule, when we cannot sign for
|
|
||||||
/// the entire schedule at once, to a single signing set isn't feasible.
|
|
||||||
///
|
|
||||||
/// While any member of the active signing set can provide data enabling other signers to
|
|
||||||
/// participate, it's several KB of data which we then have to code communication for.
|
|
||||||
/// The other option is to simply not observe S[0]. Instead, observe a TX with an identical
|
|
||||||
/// output to the one in S[0] we intended to use for S[1]. It's either from S[0], or Eve, a
|
|
||||||
/// malicious actor, has sent us a forged TX which is... equally as usable? So who cares?
|
|
||||||
///
|
|
||||||
/// The only issue is if we have multiple outputs on-chain with identical amounts and purposes.
|
|
||||||
/// Accordingly, when the scheduler makes a plan for when a specific output is available, it
|
|
||||||
/// shouldn't set that plan. It should *push* that plan to a queue of plans to perform when
|
|
||||||
/// instances of that output occur.
|
|
||||||
Branch,
|
|
||||||
|
|
||||||
/// A change output.
|
|
||||||
///
|
|
||||||
/// This should be added to the available UTXO pool with no further action taken. It does not
|
|
||||||
/// need to be reported (though we do still need synchrony on the block it's in). There's no
|
|
||||||
/// explicit expectation for the usage of this output at time of recipience.
|
|
||||||
Change,
|
|
||||||
|
|
||||||
/// A forwarded output from the prior multisig.
|
|
||||||
///
|
|
||||||
/// This is distinguished for technical reasons around detecting when a multisig should be
|
|
||||||
/// retired.
|
|
||||||
Forwarded,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OutputType {
|
|
||||||
fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
|
|
||||||
writer.write_all(&[match self {
|
|
||||||
OutputType::External => 0,
|
|
||||||
OutputType::Branch => 1,
|
|
||||||
OutputType::Change => 2,
|
|
||||||
OutputType::Forwarded => 3,
|
|
||||||
}])
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> {
|
|
||||||
let mut byte = [0; 1];
|
|
||||||
reader.read_exact(&mut byte)?;
|
|
||||||
Ok(match byte[0] {
|
|
||||||
0 => OutputType::External,
|
|
||||||
1 => OutputType::Branch,
|
|
||||||
2 => OutputType::Change,
|
|
||||||
3 => OutputType::Forwarded,
|
|
||||||
_ => Err(io::Error::other("invalid OutputType"))?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A received output.
|
|
||||||
pub trait ReceivedOutput<K: GroupEncoding, A>:
|
|
||||||
Send + Sync + Sized + Clone + PartialEq + Eq + Debug
|
|
||||||
{
|
|
||||||
/// The type used to identify this output.
|
|
||||||
type Id: 'static + Id;
|
|
||||||
|
|
||||||
/// The type of this output.
|
|
||||||
fn kind(&self) -> OutputType;
|
|
||||||
|
|
||||||
/// The ID of this output.
|
|
||||||
fn id(&self) -> Self::Id;
|
|
||||||
/// The key this output was received by.
|
|
||||||
fn key(&self) -> K;
|
|
||||||
|
|
||||||
/// The presumed origin for this output.
|
|
||||||
///
|
|
||||||
/// This is used as the address to refund coins to if we can't handle the output as desired
|
|
||||||
/// (unless overridden).
|
|
||||||
fn presumed_origin(&self) -> Option<A>;
|
|
||||||
|
|
||||||
/// The balance associated with this output.
|
|
||||||
fn balance(&self) -> Balance;
|
|
||||||
/// The arbitrary data (presumably an InInstruction) associated with this output.
|
|
||||||
fn data(&self) -> &[u8];
|
|
||||||
|
|
||||||
/// Write this output.
|
|
||||||
fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()>;
|
|
||||||
/// Read an output.
|
|
||||||
fn read<R: io::Read>(reader: &mut R) -> io::Result<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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.
|
|
||||||
fn id(&self) -> Self::Id;
|
|
||||||
/// The ID of the parent block.
|
|
||||||
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<Self::Key, Self::Address>;
|
|
||||||
|
|
||||||
/// The ID of this block.
|
|
||||||
fn id(&self) -> <Self::Header as BlockHeader>::Id;
|
|
||||||
|
|
||||||
/// Scan all outputs within this block to find the outputs spendable by this key.
|
|
||||||
fn scan_for_outputs(&self, key: Self::Key) -> Vec<Self::Output>;
|
|
||||||
}
|
|
||||||
|
|
113
processor/primitives/src/output.rs
Normal file
113
processor/primitives/src/output.rs
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
use core::fmt::Debug;
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
use group::GroupEncoding;
|
||||||
|
|
||||||
|
use serai_primitives::Balance;
|
||||||
|
|
||||||
|
use crate::Id;
|
||||||
|
|
||||||
|
/// The type of the output.
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||||
|
pub enum OutputType {
|
||||||
|
/// An output received to the address external payments use.
|
||||||
|
///
|
||||||
|
/// This is reported to Substrate in a `Batch`.
|
||||||
|
External,
|
||||||
|
|
||||||
|
/// A branch output.
|
||||||
|
///
|
||||||
|
/// Given a known output set, and a known series of outbound transactions, we should be able to
|
||||||
|
/// form a completely deterministic schedule S. The issue is when S has TXs which spend prior TXs
|
||||||
|
/// in S (which is needed for our logarithmic scheduling). In order to have the descendant TX,
|
||||||
|
/// say S[1], build off S[0], we need to observe when S[0] is included on-chain.
|
||||||
|
///
|
||||||
|
/// We cannot.
|
||||||
|
///
|
||||||
|
/// Monero (and other privacy coins) do not expose their UTXO graphs. Even if we know how to
|
||||||
|
/// create S[0], and the actual payment info behind it, we cannot observe it on the blockchain
|
||||||
|
/// unless we participated in creating it. Locking the entire schedule, when we cannot sign for
|
||||||
|
/// the entire schedule at once, to a single signing set isn't feasible.
|
||||||
|
///
|
||||||
|
/// While any member of the active signing set can provide data enabling other signers to
|
||||||
|
/// participate, it's several KB of data which we then have to code communication for.
|
||||||
|
/// The other option is to simply not observe S[0]. Instead, observe a TX with an identical
|
||||||
|
/// output to the one in S[0] we intended to use for S[1]. It's either from S[0], or Eve, a
|
||||||
|
/// malicious actor, has sent us a forged TX which is... equally as usable? So who cares?
|
||||||
|
///
|
||||||
|
/// The only issue is if we have multiple outputs on-chain with identical amounts and purposes.
|
||||||
|
/// Accordingly, when the scheduler makes a plan for when a specific output is available, it
|
||||||
|
/// shouldn't set that plan. It should *push* that plan to a queue of plans to perform when
|
||||||
|
/// instances of that output occur.
|
||||||
|
Branch,
|
||||||
|
|
||||||
|
/// A change output.
|
||||||
|
///
|
||||||
|
/// This should be added to the available UTXO pool with no further action taken. It does not
|
||||||
|
/// need to be reported (though we do still need synchrony on the block it's in). There's no
|
||||||
|
/// explicit expectation for the usage of this output at time of recipience.
|
||||||
|
Change,
|
||||||
|
|
||||||
|
/// A forwarded output from the prior multisig.
|
||||||
|
///
|
||||||
|
/// This is distinguished for technical reasons around detecting when a multisig should be
|
||||||
|
/// retired.
|
||||||
|
Forwarded,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OutputType {
|
||||||
|
/// Write the OutputType.
|
||||||
|
pub fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
|
||||||
|
writer.write_all(&[match self {
|
||||||
|
OutputType::External => 0,
|
||||||
|
OutputType::Branch => 1,
|
||||||
|
OutputType::Change => 2,
|
||||||
|
OutputType::Forwarded => 3,
|
||||||
|
}])
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read an OutputType.
|
||||||
|
pub fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> {
|
||||||
|
let mut byte = [0; 1];
|
||||||
|
reader.read_exact(&mut byte)?;
|
||||||
|
Ok(match byte[0] {
|
||||||
|
0 => OutputType::External,
|
||||||
|
1 => OutputType::Branch,
|
||||||
|
2 => OutputType::Change,
|
||||||
|
3 => OutputType::Forwarded,
|
||||||
|
_ => Err(io::Error::other("invalid OutputType"))?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A received output.
|
||||||
|
pub trait ReceivedOutput<K: GroupEncoding, A>:
|
||||||
|
Send + Sync + Sized + Clone + PartialEq + Eq + Debug
|
||||||
|
{
|
||||||
|
/// The type used to identify this output.
|
||||||
|
type Id: 'static + Id;
|
||||||
|
|
||||||
|
/// The type of this output.
|
||||||
|
fn kind(&self) -> OutputType;
|
||||||
|
|
||||||
|
/// The ID of this output.
|
||||||
|
fn id(&self) -> Self::Id;
|
||||||
|
/// The key this output was received by.
|
||||||
|
fn key(&self) -> K;
|
||||||
|
|
||||||
|
/// The presumed origin for this output.
|
||||||
|
///
|
||||||
|
/// This is used as the address to refund coins to if we can't handle the output as desired
|
||||||
|
/// (unless overridden).
|
||||||
|
fn presumed_origin(&self) -> Option<A>;
|
||||||
|
|
||||||
|
/// The balance associated with this output.
|
||||||
|
fn balance(&self) -> Balance;
|
||||||
|
/// The arbitrary data (presumably an InInstruction) associated with this output.
|
||||||
|
fn data(&self) -> &[u8];
|
||||||
|
|
||||||
|
/// Write this output.
|
||||||
|
fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()>;
|
||||||
|
/// Read an output.
|
||||||
|
fn read<R: io::Read>(reader: &mut R) -> io::Result<Self>;
|
||||||
|
}
|
Loading…
Reference in a new issue