From f2ee4daf430721e0a637d4b7db4271c040dc48e9 Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Thu, 22 Aug 2024 01:27:57 -0400 Subject: [PATCH] Add Eventuality back to processor primitives Also splits crate into modules. --- processor/primitives/src/block.rs | 40 +++++++ processor/primitives/src/eventuality.rs | 31 +++++ processor/primitives/src/lib.rs | 152 ++---------------------- processor/primitives/src/output.rs | 113 ++++++++++++++++++ 4 files changed, 194 insertions(+), 142 deletions(-) create mode 100644 processor/primitives/src/block.rs create mode 100644 processor/primitives/src/eventuality.rs create mode 100644 processor/primitives/src/output.rs diff --git a/processor/primitives/src/block.rs b/processor/primitives/src/block.rs new file mode 100644 index 00000000..22f0b998 --- /dev/null +++ b/processor/primitives/src/block.rs @@ -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; + + /// 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; +} diff --git a/processor/primitives/src/eventuality.rs b/processor/primitives/src/eventuality.rs new file mode 100644 index 00000000..6e16637d --- /dev/null +++ b/processor/primitives/src/eventuality.rs @@ -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; + + /// Read an Eventuality. + fn read(reader: &mut R) -> io::Result; + /// Serialize an Eventuality to a `Vec`. + fn serialize(&self) -> Vec; +} + +/// A tracker of unresolved Eventualities. +#[derive(Debug)] +pub struct EventualityTracker { + /// The active Eventualities. + /// + /// These are keyed by their lookups. + pub active_eventualities: HashMap, E>, +} diff --git a/processor/primitives/src/lib.rs b/processor/primitives/src/lib.rs index 744aae47..dc64facf 100644 --- a/processor/primitives/src/lib.rs +++ b/processor/primitives/src/lib.rs @@ -3,15 +3,21 @@ #![deny(missing_docs)] use core::fmt::Debug; -use std::io; -use group::{Group, GroupEncoding}; - -use serai_primitives::Balance; +use group::GroupEncoding; use scale::{Encode, Decode}; 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. /// /// IDs don't need to implement `Copy`, enabling `[u8; 33]`, `[u8; 64]` to be used. IDs are still @@ -51,141 +57,3 @@ impl BorshDeserialize for BorshG { )) } } - -/// 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(&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(reader: &mut R) -> io::Result { - 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: - 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; - - /// 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(&self, writer: &mut W) -> io::Result<()>; - /// Read an output. - fn read(reader: &mut R) -> io::Result; -} - -/// 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; - - /// 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; -} diff --git a/processor/primitives/src/output.rs b/processor/primitives/src/output.rs new file mode 100644 index 00000000..1dd186aa --- /dev/null +++ b/processor/primitives/src/output.rs @@ -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(&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(reader: &mut R) -> io::Result { + 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: + 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; + + /// 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(&self, writer: &mut W) -> io::Result<()>; + /// Read an output. + fn read(reader: &mut R) -> io::Result; +}