mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Definition and delineation of tasks within the scanner
Also defines primitives for the processor.
This commit is contained in:
27
processor/primitives/Cargo.toml
Normal file
27
processor/primitives/Cargo.toml
Normal file
@@ -0,0 +1,27 @@
|
||||
[package]
|
||||
name = "serai-processor-primitives"
|
||||
version = "0.1.0"
|
||||
description = "Primitives for the Serai processor"
|
||||
license = "AGPL-3.0-only"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/processor/primitives"
|
||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||
keywords = []
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
async-trait = { version = "0.1", default-features = false }
|
||||
|
||||
group = { version = "0.13", default-features = false }
|
||||
|
||||
serai-primitives = { path = "../../substrate/primitives", default-features = false, features = ["std"] }
|
||||
|
||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["std"] }
|
||||
borsh = { version = "1", default-features = false, features = ["std", "derive", "de_strict_order"] }
|
||||
15
processor/primitives/LICENSE
Normal file
15
processor/primitives/LICENSE
Normal file
@@ -0,0 +1,15 @@
|
||||
AGPL-3.0-only license
|
||||
|
||||
Copyright (c) 2022-2024 Luke Parker
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License Version 3 as
|
||||
published by the Free Software Foundation.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
3
processor/primitives/README.md
Normal file
3
processor/primitives/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Primitives
|
||||
|
||||
Primitive types/traits/structs used by the Processor.
|
||||
167
processor/primitives/src/lib.rs
Normal file
167
processor/primitives/src/lib.rs
Normal file
@@ -0,0 +1,167 @@
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use core::fmt::Debug;
|
||||
use std::io;
|
||||
|
||||
use group::GroupEncoding;
|
||||
|
||||
use serai_primitives::Balance;
|
||||
|
||||
use scale::{Encode, Decode};
|
||||
use borsh::{BorshSerialize, BorshDeserialize};
|
||||
|
||||
/// 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
|
||||
/// bound to being of a constant-size, where `Default::default()` returns an instance of such size
|
||||
/// (making `Vec<u8>` invalid as an `Id`).
|
||||
pub trait Id:
|
||||
Send
|
||||
+ Sync
|
||||
+ Clone
|
||||
+ Default
|
||||
+ PartialEq
|
||||
+ AsRef<[u8]>
|
||||
+ AsMut<[u8]>
|
||||
+ Debug
|
||||
+ Encode
|
||||
+ Decode
|
||||
+ BorshSerialize
|
||||
+ BorshDeserialize
|
||||
{
|
||||
}
|
||||
impl<const N: usize> Id for [u8; N] where [u8; N]: Default {}
|
||||
|
||||
/// 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 from an external network.
|
||||
#[async_trait::async_trait]
|
||||
pub trait Block: 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 wrapper for a group element which implements the borsh traits.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub struct BorshG<G: GroupEncoding>(pub G);
|
||||
impl<G: GroupEncoding> BorshSerialize for BorshG<G> {
|
||||
fn serialize<W: borsh::io::Write>(&self, writer: &mut W) -> borsh::io::Result<()> {
|
||||
writer.write_all(self.0.to_bytes().as_ref())
|
||||
}
|
||||
}
|
||||
impl<G: GroupEncoding> BorshDeserialize for BorshG<G> {
|
||||
fn deserialize_reader<R: borsh::io::Read>(reader: &mut R) -> borsh::io::Result<Self> {
|
||||
let mut repr = G::Repr::default();
|
||||
reader.read_exact(repr.as_mut())?;
|
||||
Ok(Self(
|
||||
Option::<G>::from(G::from_bytes(&repr)).ok_or(borsh::io::Error::other("invalid point"))?,
|
||||
))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user