mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-09 04:39:24 +00:00
We need the TX publication relay thingy for send to work (though that is the point the test fails at).
209 lines
5.5 KiB
Rust
209 lines
5.5 KiB
Rust
use std::{io, collections::HashSet};
|
|
|
|
use ciphersuite::{group::GroupEncoding, Ciphersuite};
|
|
|
|
use serai_client::primitives::{NetworkId, Coin, Balance};
|
|
|
|
use crate::{
|
|
Get, DbTxn, Db, Payment, Plan, create_db,
|
|
networks::{Output, Network},
|
|
multisigs::scheduler::{SchedulerAddendum, Scheduler as SchedulerTrait},
|
|
};
|
|
|
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
|
pub struct Scheduler<N: Network> {
|
|
key: <N::Curve as Ciphersuite>::G,
|
|
coins: HashSet<Coin>,
|
|
rotated: bool,
|
|
}
|
|
|
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
|
pub enum Addendum<N: Network> {
|
|
Nonce(u64),
|
|
RotateTo { nonce: u64, new_key: <N::Curve as Ciphersuite>::G },
|
|
}
|
|
|
|
impl<N: Network> SchedulerAddendum for Addendum<N> {
|
|
fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> {
|
|
let mut kind = [0xff];
|
|
reader.read_exact(&mut kind)?;
|
|
match kind[0] {
|
|
0 => {
|
|
let mut nonce = [0; 8];
|
|
reader.read_exact(&mut nonce)?;
|
|
Ok(Addendum::Nonce(u64::from_le_bytes(nonce)))
|
|
}
|
|
1 => {
|
|
let mut nonce = [0; 8];
|
|
reader.read_exact(&mut nonce)?;
|
|
let nonce = u64::from_le_bytes(nonce);
|
|
|
|
let new_key = N::Curve::read_G(reader)?;
|
|
Ok(Addendum::RotateTo { nonce, new_key })
|
|
}
|
|
_ => Err(io::Error::other("reading unknown Addendum type"))?,
|
|
}
|
|
}
|
|
fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
|
|
match self {
|
|
Addendum::Nonce(nonce) => {
|
|
writer.write_all(&[0])?;
|
|
writer.write_all(&nonce.to_le_bytes())
|
|
}
|
|
Addendum::RotateTo { nonce, new_key } => {
|
|
writer.write_all(&[1])?;
|
|
writer.write_all(&nonce.to_le_bytes())?;
|
|
writer.write_all(new_key.to_bytes().as_ref())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
create_db! {
|
|
SchedulerDb {
|
|
LastNonce: () -> u64,
|
|
RotatedTo: (key: &[u8]) -> Vec<u8>,
|
|
}
|
|
}
|
|
|
|
impl<N: Network<Scheduler = Self>> SchedulerTrait<N> for Scheduler<N> {
|
|
type Addendum = Addendum<N>;
|
|
|
|
/// Check if this Scheduler is empty.
|
|
fn empty(&self) -> bool {
|
|
self.rotated
|
|
}
|
|
|
|
/// Create a new Scheduler.
|
|
fn new<D: Db>(
|
|
_txn: &mut D::Transaction<'_>,
|
|
key: <N::Curve as Ciphersuite>::G,
|
|
network: NetworkId,
|
|
) -> Self {
|
|
assert!(N::branch_address(key).is_none());
|
|
assert!(N::change_address(key).is_none());
|
|
assert!(N::forward_address(key).is_none());
|
|
|
|
Scheduler { key, coins: network.coins().iter().copied().collect(), rotated: false }
|
|
}
|
|
|
|
/// Load a Scheduler from the DB.
|
|
fn from_db<D: Db>(
|
|
db: &D,
|
|
key: <N::Curve as Ciphersuite>::G,
|
|
network: NetworkId,
|
|
) -> io::Result<Self> {
|
|
Ok(Scheduler {
|
|
key,
|
|
coins: network.coins().iter().copied().collect(),
|
|
rotated: RotatedTo::get(db, key.to_bytes().as_ref()).is_some(),
|
|
})
|
|
}
|
|
|
|
fn can_use_branch(&self, _balance: Balance) -> bool {
|
|
false
|
|
}
|
|
|
|
fn schedule<D: Db>(
|
|
&mut self,
|
|
txn: &mut D::Transaction<'_>,
|
|
utxos: Vec<N::Output>,
|
|
payments: Vec<Payment<N>>,
|
|
key_for_any_change: <N::Curve as Ciphersuite>::G,
|
|
force_spend: bool,
|
|
) -> Vec<Plan<N>> {
|
|
for utxo in utxos {
|
|
assert!(self.coins.contains(&utxo.balance().coin));
|
|
}
|
|
|
|
let mut nonce = LastNonce::get(txn).unwrap_or(1);
|
|
let mut plans = vec![];
|
|
for chunk in payments.as_slice().chunks(N::MAX_OUTPUTS) {
|
|
// Once we rotate, all further payments should be scheduled via the new multisig
|
|
assert!(!self.rotated);
|
|
plans.push(Plan {
|
|
key: self.key,
|
|
inputs: vec![],
|
|
payments: chunk.to_vec(),
|
|
change: None,
|
|
scheduler_addendum: Addendum::Nonce(nonce),
|
|
});
|
|
nonce += 1;
|
|
}
|
|
|
|
// If we're supposed to rotate to the new key, create an empty Plan which will signify the key
|
|
// update
|
|
if force_spend && (!self.rotated) {
|
|
plans.push(Plan {
|
|
key: self.key,
|
|
inputs: vec![],
|
|
payments: vec![],
|
|
change: None,
|
|
scheduler_addendum: Addendum::RotateTo { nonce, new_key: key_for_any_change },
|
|
});
|
|
nonce += 1;
|
|
self.rotated = true;
|
|
RotatedTo::set(
|
|
txn,
|
|
self.key.to_bytes().as_ref(),
|
|
&key_for_any_change.to_bytes().as_ref().to_vec(),
|
|
);
|
|
}
|
|
|
|
LastNonce::set(txn, &nonce);
|
|
|
|
plans
|
|
}
|
|
|
|
fn consume_payments<D: Db>(&mut self, _txn: &mut D::Transaction<'_>) -> Vec<Payment<N>> {
|
|
vec![]
|
|
}
|
|
|
|
fn created_output<D: Db>(
|
|
&mut self,
|
|
_txn: &mut D::Transaction<'_>,
|
|
_expected: u64,
|
|
_actual: Option<u64>,
|
|
) {
|
|
panic!("Smart Contract Scheduler created a Branch output")
|
|
}
|
|
|
|
/// Refund a specific output.
|
|
fn refund_plan<D: Db>(
|
|
&mut self,
|
|
txn: &mut D::Transaction<'_>,
|
|
output: N::Output,
|
|
refund_to: N::Address,
|
|
) -> Plan<N> {
|
|
let current_key = RotatedTo::get(txn, self.key.to_bytes().as_ref())
|
|
.and_then(|key_bytes| <N::Curve as Ciphersuite>::read_G(&mut key_bytes.as_slice()).ok())
|
|
.unwrap_or(self.key);
|
|
|
|
let nonce = LastNonce::get(txn).map_or(1, |nonce| nonce + 1);
|
|
LastNonce::set(txn, &(nonce + 1));
|
|
Plan {
|
|
key: current_key,
|
|
inputs: vec![],
|
|
payments: vec![Payment { address: refund_to, data: None, balance: output.balance() }],
|
|
change: None,
|
|
scheduler_addendum: Addendum::Nonce(nonce),
|
|
}
|
|
}
|
|
|
|
fn shim_forward_plan(_output: N::Output, _to: <N::Curve as Ciphersuite>::G) -> Option<Plan<N>> {
|
|
None
|
|
}
|
|
|
|
/// Forward a specific output to the new multisig.
|
|
///
|
|
/// Returns None if no forwarding is necessary.
|
|
fn forward_plan<D: Db>(
|
|
&mut self,
|
|
_txn: &mut D::Transaction<'_>,
|
|
_output: N::Output,
|
|
_to: <N::Curve as Ciphersuite>::G,
|
|
) -> Option<Plan<N>> {
|
|
None
|
|
}
|
|
}
|