mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Smart Contract Scheduler
This commit is contained in:
34
processor/scheduler/smart-contract/Cargo.toml
Normal file
34
processor/scheduler/smart-contract/Cargo.toml
Normal file
@@ -0,0 +1,34 @@
|
||||
[package]
|
||||
name = "serai-processor-smart-contract-scheduler"
|
||||
version = "0.1.0"
|
||||
description = "Scheduler for a smart contract representing the Serai processor"
|
||||
license = "AGPL-3.0-only"
|
||||
repository = "https://github.com/serai-dex/serai/tree/develop/processor/scheduler/smart-contract"
|
||||
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||
keywords = []
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = ["scale", "borsh"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
group = { version = "0.13", default-features = false }
|
||||
|
||||
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["std"] }
|
||||
borsh = { version = "1", default-features = false, features = ["std", "derive", "de_strict_order"] }
|
||||
|
||||
serai-primitives = { path = "../../../substrate/primitives", default-features = false, features = ["std"] }
|
||||
|
||||
serai-db = { path = "../../../common/db" }
|
||||
|
||||
primitives = { package = "serai-processor-primitives", path = "../../primitives" }
|
||||
scanner = { package = "serai-processor-scanner", path = "../../scanner" }
|
||||
scheduler-primitives = { package = "serai-processor-scheduler-primitives", path = "../primitives" }
|
||||
15
processor/scheduler/smart-contract/LICENSE
Normal file
15
processor/scheduler/smart-contract/LICENSE
Normal file
@@ -0,0 +1,15 @@
|
||||
AGPL-3.0-only license
|
||||
|
||||
Copyright (c) 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/scheduler/smart-contract/README.md
Normal file
3
processor/scheduler/smart-contract/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Smart Contract Scheduler
|
||||
|
||||
A scheduler for a smart contract representing the Serai processor.
|
||||
136
processor/scheduler/smart-contract/src/lib.rs
Normal file
136
processor/scheduler/smart-contract/src/lib.rs
Normal file
@@ -0,0 +1,136 @@
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![doc = include_str!("../README.md")]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use core::{marker::PhantomData, future::Future};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use group::GroupEncoding;
|
||||
|
||||
use serai_db::{Get, DbTxn, create_db};
|
||||
|
||||
use primitives::{ReceivedOutput, Payment};
|
||||
use scanner::{
|
||||
LifetimeStage, ScannerFeed, KeyFor, AddressFor, EventualityFor, BlockFor, SchedulerUpdate,
|
||||
KeyScopedEventualities, Scheduler as SchedulerTrait,
|
||||
};
|
||||
use scheduler_primitives::*;
|
||||
|
||||
create_db! {
|
||||
SmartContractScheduler {
|
||||
NextNonce: () -> u64,
|
||||
}
|
||||
}
|
||||
|
||||
/// A smart contract.
|
||||
pub trait SmartContract<S: ScannerFeed>: 'static + Send {
|
||||
/// The type representing a signable transaction.
|
||||
type SignableTransaction: SignableTransaction;
|
||||
|
||||
/// Rotate from the retiring key to the new key.
|
||||
fn rotate(
|
||||
nonce: u64,
|
||||
retiring_key: KeyFor<S>,
|
||||
new_key: KeyFor<S>,
|
||||
) -> (Self::SignableTransaction, EventualityFor<S>);
|
||||
/// Fulfill the set of payments, dropping any not worth handling.
|
||||
fn fulfill(
|
||||
starting_nonce: u64,
|
||||
payments: Vec<Payment<AddressFor<S>>>,
|
||||
) -> Vec<(Self::SignableTransaction, EventualityFor<S>)>;
|
||||
}
|
||||
|
||||
/// A scheduler for a smart contract representing the Serai processor.
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Scheduler<S: ScannerFeed, SC: SmartContract<S>> {
|
||||
_S: PhantomData<S>,
|
||||
_SC: PhantomData<SC>,
|
||||
}
|
||||
|
||||
fn fulfill_payments<S: ScannerFeed, SC: SmartContract<S>>(
|
||||
txn: &mut impl DbTxn,
|
||||
active_keys: &[(KeyFor<S>, LifetimeStage)],
|
||||
payments: Vec<Payment<AddressFor<S>>>,
|
||||
) -> KeyScopedEventualities<S> {
|
||||
let key = match active_keys[0].1 {
|
||||
LifetimeStage::ActiveYetNotReporting |
|
||||
LifetimeStage::Active |
|
||||
LifetimeStage::UsingNewForChange => active_keys[0].0,
|
||||
LifetimeStage::Forwarding | LifetimeStage::Finishing => active_keys[1].0,
|
||||
};
|
||||
|
||||
let mut nonce = NextNonce::get(txn).unwrap_or(0);
|
||||
let mut eventualities = Vec::with_capacity(1);
|
||||
for (signable, eventuality) in SC::fulfill(nonce, payments) {
|
||||
TransactionsToSign::<SC::SignableTransaction>::send(txn, &key, &signable);
|
||||
nonce += 1;
|
||||
eventualities.push(eventuality);
|
||||
}
|
||||
NextNonce::set(txn, &nonce);
|
||||
HashMap::from([(key.to_bytes().as_ref().to_vec(), eventualities)])
|
||||
}
|
||||
|
||||
impl<S: ScannerFeed, SC: SmartContract<S>> SchedulerTrait<S> for Scheduler<S, SC> {
|
||||
type EphemeralError = ();
|
||||
type SignableTransaction = SC::SignableTransaction;
|
||||
|
||||
fn activate_key(_txn: &mut impl DbTxn, _key: KeyFor<S>) {}
|
||||
|
||||
fn flush_key(
|
||||
&self,
|
||||
txn: &mut impl DbTxn,
|
||||
_block: &BlockFor<S>,
|
||||
retiring_key: KeyFor<S>,
|
||||
new_key: KeyFor<S>,
|
||||
) -> impl Send + Future<Output = Result<KeyScopedEventualities<S>, Self::EphemeralError>> {
|
||||
async move {
|
||||
let nonce = NextNonce::get(txn).unwrap_or(0);
|
||||
let (signable, eventuality) = SC::rotate(nonce, retiring_key, new_key);
|
||||
NextNonce::set(txn, &(nonce + 1));
|
||||
TransactionsToSign::<SC::SignableTransaction>::send(txn, &retiring_key, &signable);
|
||||
Ok(HashMap::from([(retiring_key.to_bytes().as_ref().to_vec(), vec![eventuality])]))
|
||||
}
|
||||
}
|
||||
|
||||
fn retire_key(_txn: &mut impl DbTxn, _key: KeyFor<S>) {}
|
||||
|
||||
fn update(
|
||||
&self,
|
||||
txn: &mut impl DbTxn,
|
||||
_block: &BlockFor<S>,
|
||||
active_keys: &[(KeyFor<S>, LifetimeStage)],
|
||||
update: SchedulerUpdate<S>,
|
||||
) -> impl Send + Future<Output = Result<KeyScopedEventualities<S>, Self::EphemeralError>> {
|
||||
async move {
|
||||
// We ignore the outputs as we don't need to know our current state as it never suffers
|
||||
// partial availability
|
||||
|
||||
// We shouldn't have any forwards though
|
||||
assert!(update.forwards().is_empty());
|
||||
|
||||
// Create the transactions for the returns
|
||||
Ok(fulfill_payments::<S, SC>(
|
||||
txn,
|
||||
active_keys,
|
||||
update
|
||||
.returns()
|
||||
.iter()
|
||||
.map(|to_return| {
|
||||
Payment::new(to_return.address().clone(), to_return.output().balance(), None)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn fulfill(
|
||||
&self,
|
||||
txn: &mut impl DbTxn,
|
||||
_block: &BlockFor<S>,
|
||||
active_keys: &[(KeyFor<S>, LifetimeStage)],
|
||||
payments: Vec<Payment<AddressFor<S>>>,
|
||||
) -> impl Send + Future<Output = Result<KeyScopedEventualities<S>, Self::EphemeralError>> {
|
||||
async move { Ok(fulfill_payments::<S, SC>(txn, active_keys, payments)) }
|
||||
}
|
||||
}
|
||||
@@ -470,7 +470,7 @@ impl<S: ScannerFeed, P: TransactionPlanner<S, ()>> SchedulerTrait<S> for Schedul
|
||||
}
|
||||
}
|
||||
|
||||
// Create the transactions for the forwards/burns
|
||||
// Create the transactions for the forwards/returns
|
||||
{
|
||||
let mut planned_txs = vec![];
|
||||
for forward in update.forwards() {
|
||||
|
||||
@@ -488,7 +488,7 @@ impl<S: ScannerFeed, P: TransactionPlanner<S, EffectedReceivedOutputs<S>>> Sched
|
||||
}
|
||||
}
|
||||
|
||||
// Create the transactions for the forwards/burns
|
||||
// Create the transactions for the forwards/returns
|
||||
{
|
||||
let mut planned_txs = vec![];
|
||||
for forward in update.forwards() {
|
||||
|
||||
Reference in New Issue
Block a user